@@ -356,6 +356,141 @@ describeIntegration("Workspace deletion integration tests", () => {
356356 TEST_TIMEOUT
357357 ) ;
358358
359+ test . concurrent (
360+ "should fail to delete SSH workspace with unpushed refs without force flag" ,
361+ async ( ) => {
362+ // This test only applies to SSH runtime (local worktrees share .git so unpushed refs are safe)
363+ if ( type !== "ssh" ) {
364+ return ;
365+ }
366+
367+ const env = await createTestEnvironment ( ) ;
368+ const tempGitRepo = await createTempGitRepo ( ) ;
369+
370+ try {
371+ const branchName = generateBranchName ( "delete-unpushed" ) ;
372+ const runtimeConfig = getRuntimeConfig ( branchName ) ;
373+ const { workspaceId } = await createWorkspaceWithInit (
374+ env ,
375+ tempGitRepo ,
376+ branchName ,
377+ runtimeConfig ,
378+ true , // waitForInit
379+ true // isSSH
380+ ) ;
381+
382+ // Configure git for committing (SSH environment needs this)
383+ await executeBash ( env , workspaceId , 'git config user.email "test@example.com"' ) ;
384+ await executeBash ( env , workspaceId , 'git config user.name "Test User"' ) ;
385+
386+ // Add a fake remote (needed for unpushed check to work)
387+ // Without a remote, SSH workspaces have no concept of "unpushed" commits
388+ await executeBash (
389+ env ,
390+ workspaceId ,
391+ "git remote add origin https://github.com/fake/repo.git"
392+ ) ;
393+
394+ // Create a commit in the workspace (unpushed)
395+ await executeBash ( env , workspaceId , 'echo "new content" > newfile.txt' ) ;
396+ await executeBash ( env , workspaceId , "git add newfile.txt" ) ;
397+ await executeBash ( env , workspaceId , 'git commit -m "Unpushed commit"' ) ;
398+
399+ // Verify commit was created and working tree is clean
400+ const statusResult = await executeBash ( env , workspaceId , "git status --porcelain" ) ;
401+ expect ( statusResult . output . trim ( ) ) . toBe ( "" ) ; // Should be clean
402+
403+ // Attempt to delete without force should fail
404+ const deleteResult = await env . mockIpcRenderer . invoke (
405+ IPC_CHANNELS . WORKSPACE_REMOVE ,
406+ workspaceId
407+ ) ;
408+ expect ( deleteResult . success ) . toBe ( false ) ;
409+ expect ( deleteResult . error ) . toMatch ( / u n p u s h e d .* c o m m i t | u n p u s h e d .* r e f / i) ;
410+
411+ // Verify workspace still exists
412+ const stillExists = await workspaceExists ( env , workspaceId ) ;
413+ expect ( stillExists ) . toBe ( true ) ;
414+
415+ // Cleanup: force delete for cleanup
416+ await env . mockIpcRenderer . invoke ( IPC_CHANNELS . WORKSPACE_REMOVE , workspaceId , {
417+ force : true ,
418+ } ) ;
419+ } finally {
420+ await cleanupTestEnvironment ( env ) ;
421+ await cleanupTempGitRepo ( tempGitRepo ) ;
422+ }
423+ } ,
424+ TEST_TIMEOUT
425+ ) ;
426+
427+ test . concurrent (
428+ "should delete SSH workspace with unpushed refs when force flag is set" ,
429+ async ( ) => {
430+ // This test only applies to SSH runtime
431+ if ( type !== "ssh" ) {
432+ return ;
433+ }
434+
435+ const env = await createTestEnvironment ( ) ;
436+ const tempGitRepo = await createTempGitRepo ( ) ;
437+
438+ try {
439+ const branchName = generateBranchName ( "delete-unpushed-force" ) ;
440+ const runtimeConfig = getRuntimeConfig ( branchName ) ;
441+ const { workspaceId } = await createWorkspaceWithInit (
442+ env ,
443+ tempGitRepo ,
444+ branchName ,
445+ runtimeConfig ,
446+ true , // waitForInit
447+ true // isSSH
448+ ) ;
449+
450+ // Configure git for committing (SSH environment needs this)
451+ await executeBash ( env , workspaceId , 'git config user.email "test@example.com"' ) ;
452+ await executeBash ( env , workspaceId , 'git config user.name "Test User"' ) ;
453+
454+ // Add a fake remote (needed for unpushed check to work)
455+ // Without a remote, SSH workspaces have no concept of "unpushed" commits
456+ await executeBash (
457+ env ,
458+ workspaceId ,
459+ "git remote add origin https://github.com/fake/repo.git"
460+ ) ;
461+
462+ // Create a commit in the workspace (unpushed)
463+ await executeBash ( env , workspaceId , 'echo "new content" > newfile.txt' ) ;
464+ await executeBash ( env , workspaceId , "git add newfile.txt" ) ;
465+ await executeBash ( env , workspaceId , 'git commit -m "Unpushed commit"' ) ;
466+
467+ // Verify commit was created and working tree is clean
468+ const statusResult = await executeBash ( env , workspaceId , "git status --porcelain" ) ;
469+ expect ( statusResult . output . trim ( ) ) . toBe ( "" ) ; // Should be clean
470+
471+ // Delete with force should succeed
472+ const deleteResult = await env . mockIpcRenderer . invoke (
473+ IPC_CHANNELS . WORKSPACE_REMOVE ,
474+ workspaceId ,
475+ { force : true }
476+ ) ;
477+ expect ( deleteResult . success ) . toBe ( true ) ;
478+
479+ // Verify workspace was removed from config
480+ const config = env . config . loadConfigOrDefault ( ) ;
481+ const project = config . projects . get ( tempGitRepo ) ;
482+ if ( project ) {
483+ const stillInConfig = project . workspaces . some ( ( w ) => w . id === workspaceId ) ;
484+ expect ( stillInConfig ) . toBe ( false ) ;
485+ }
486+ } finally {
487+ await cleanupTestEnvironment ( env ) ;
488+ await cleanupTempGitRepo ( tempGitRepo ) ;
489+ }
490+ } ,
491+ TEST_TIMEOUT
492+ ) ;
493+
359494 // Submodule tests only apply to local runtime (SSH doesn't use git worktrees)
360495 if ( type === "local" ) {
361496 test . concurrent (
0 commit comments