Skip to content

Commit 9357ca3

Browse files
committed
🤖 fix: reduce task tool errors during workspace removal
Change-Id: I638aef9035b6de82d84dde49314ba15f81ee9332 Signed-off-by: Thomas Kosiewski <[email protected]>
1 parent 34e3564 commit 9357ca3

File tree

4 files changed

+53
-8
lines changed

4 files changed

+53
-8
lines changed

src/node/services/agentPresets.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const RESEARCH_PRESET: AgentPreset = {
6363
"- Do not edit files.",
6464
"- Do not run bash commands unless explicitly enabled (assume it is not).",
6565
"- If the task tool is available and you need repository exploration beyond file_read, delegate to an Explore sub-agent.",
66+
"- Use task_list only for discovery (e.g. after interruptions). Do not poll task_list to wait; use task_await to wait for completion.",
6667
],
6768
delegation: [
6869
'- If available, use: task({ subagent_type: "explore", prompt: "..." }) when you need repo exploration.',
@@ -90,6 +91,7 @@ const EXPLORE_PRESET: AgentPreset = {
9091
"- Do not edit files.",
9192
"- Treat bash as read-only: prefer commands like rg, ls, cat, git show, git diff (read-only).",
9293
"- If the task tool is available and you need external information, delegate to a Research sub-agent.",
94+
"- Use task_list only for discovery (e.g. after interruptions). Do not poll task_list to wait; use task_await to wait for completion.",
9395
],
9496
delegation: [
9597
'- If available, use: task({ subagent_type: "research", prompt: "..." }) when you need web research.',

src/node/services/backgroundProcessManager.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ export class BackgroundProcessManager extends EventEmitter<BackgroundProcessMana
107107

108108
constructor(bgOutputDir: string) {
109109
super();
110+
// Background bash status can have many concurrent subscribers (e.g. multiple workspaces).
111+
// Raise the default listener cap to avoid noisy MaxListenersExceededWarning.
112+
this.setMaxListeners(50);
110113
this.bgOutputDir = bgOutputDir;
111114
}
112115

src/node/services/streamManager.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,6 +1481,13 @@ export class StreamManager extends EventEmitter {
14811481
): Promise<Result<StreamToken, SendMessageError>> {
14821482
const typedWorkspaceId = workspaceId as WorkspaceId;
14831483

1484+
if (messages.length === 0) {
1485+
return Err({
1486+
type: "unknown",
1487+
raw: "Invalid prompt: messages must not be empty",
1488+
});
1489+
}
1490+
14841491
// Get or create mutex for this workspace
14851492
if (!this.streamLocks.has(typedWorkspaceId)) {
14861493
this.streamLocks.set(typedWorkspaceId, new AsyncMutex());

src/node/services/workspaceService.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)