@@ -113,6 +113,8 @@ export class WorkspaceService extends EventEmitter {
113113 private readonly postCompactionRefreshTimers = new Map < string , NodeJS . Timeout > ( ) ;
114114 // Tracks workspaces currently being renamed to prevent streaming during rename
115115 private readonly renamingWorkspaces = new Set < string > ( ) ;
116+ // Tracks workspaces currently being removed to prevent new sessions/streams during deletion.
117+ private readonly removingWorkspaces = new Set < string > ( ) ;
116118
117119 constructor (
118120 private readonly config : Config ,
@@ -631,18 +633,19 @@ export class WorkspaceService extends EventEmitter {
631633 }
632634
633635 async remove ( workspaceId : string , force = false ) : Promise < Result < void > > {
636+ const wasRemoving = this . removingWorkspaces . has ( workspaceId ) ;
637+ this . removingWorkspaces . add ( workspaceId ) ;
638+
634639 // Try to remove from runtime (filesystem)
635640 try {
636641 // Stop any active stream before deleting metadata/config to avoid tool calls racing with removal.
637642 try {
638- if ( this . aiService . isStreaming ( workspaceId ) ) {
639- const stopResult = await this . aiService . stopStream ( workspaceId , { abandonPartial : true } ) ;
640- if ( ! stopResult . success ) {
641- log . debug ( "Failed to stop stream during workspace removal" , {
642- workspaceId,
643- error : stopResult . error ,
644- } ) ;
645- }
643+ const stopResult = await this . aiService . stopStream ( workspaceId , { abandonPartial : true } ) ;
644+ if ( ! stopResult . success ) {
645+ log . debug ( "Failed to stop stream during workspace removal" , {
646+ workspaceId,
647+ error : stopResult . error ,
648+ } ) ;
646649 }
647650 } catch ( error : unknown ) {
648651 log . debug ( "Failed to stop stream during workspace removal (threw)" , { workspaceId, error } ) ;
@@ -706,6 +709,10 @@ export class WorkspaceService extends EventEmitter {
706709 } catch ( error ) {
707710 const message = error instanceof Error ? error . message : String ( error ) ;
708711 return Err ( `Failed to remove workspace: ${ message } ` ) ;
712+ } finally {
713+ if ( ! wasRemoving ) {
714+ this . removingWorkspaces . delete ( workspaceId ) ;
715+ }
709716 }
710717 }
711718
@@ -1173,6 +1180,23 @@ export class WorkspaceService extends EventEmitter {
11731180 } ) ;
11741181 }
11751182
1183+ // Block streaming while workspace is being removed to prevent races with config/session deletion.
1184+ if ( this . removingWorkspaces . has ( workspaceId ) ) {
1185+ log . debug ( "sendMessage blocked: workspace is being removed" , { workspaceId } ) ;
1186+ return Err ( {
1187+ type : "unknown" ,
1188+ raw : "Workspace is being deleted. Please wait and try again." ,
1189+ } ) ;
1190+ }
1191+
1192+ // Guard: avoid creating sessions for workspaces that don't exist anymore.
1193+ if ( ! this . config . findWorkspace ( workspaceId ) ) {
1194+ return Err ( {
1195+ type : "unknown" ,
1196+ raw : "Workspace not found. It may have been deleted." ,
1197+ } ) ;
1198+ }
1199+
11761200 // Guard: queued agent tasks must not start streaming via generic sendMessage calls.
11771201 // They should only be started by TaskService once a parallel slot is available.
11781202 if ( ! internal ?. allowQueuedAgentTask ) {
@@ -1329,6 +1353,15 @@ export class WorkspaceService extends EventEmitter {
13291353 } ) ;
13301354 }
13311355
1356+ // Block streaming while workspace is being removed to prevent races with config/session deletion.
1357+ if ( this . removingWorkspaces . has ( workspaceId ) ) {
1358+ log . debug ( "resumeStream blocked: workspace is being removed" , { workspaceId } ) ;
1359+ return Err ( {
1360+ type : "unknown" ,
1361+ raw : "Workspace is being deleted. Please wait and try again." ,
1362+ } ) ;
1363+ }
1364+
13321365 // Guard: avoid creating sessions for workspaces that don't exist anymore.
13331366 if ( ! this . config . findWorkspace ( workspaceId ) ) {
13341367 return Err ( {
0 commit comments