diff --git a/src/resources/extensions/async-jobs/index.ts b/src/resources/extensions/async-jobs/index.ts index 24c379d1c..62cd4bbb4 100644 --- a/src/resources/extensions/async-jobs/index.ts +++ b/src/resources/extensions/async-jobs/index.ts @@ -78,6 +78,17 @@ export default function AsyncJobs(pi: ExtensionAPI) { }); }); + pi.on("session_before_switch", async () => { + if (manager) { + // Cancel all running background jobs — their results are no longer + // relevant to the new session and would produce wasteful follow-up + // notifications that trigger empty LLM turns (#1642). + for (const job of manager.getRunningJobs()) { + manager.cancel(job.id); + } + } + }); + pi.on("session_shutdown", async () => { if (manager) { manager.shutdown(); diff --git a/src/resources/extensions/gsd/auto-loop.ts b/src/resources/extensions/gsd/auto-loop.ts index 93220ee43..080d92451 100644 --- a/src/resources/extensions/gsd/auto-loop.ts +++ b/src/resources/extensions/gsd/auto-loop.ts @@ -287,6 +287,20 @@ export async function runUnit( status: result.status, }); + // Discard trailing follow-up messages (e.g. async_job_result notifications) + // from the completed unit. Without this, queued follow-ups trigger wasteful + // LLM turns before the next session can start (#1642). + // clearQueue() lives on AgentSession but isn't part of the typed + // ExtensionCommandContext interface — call it via runtime check. + try { + const cmdCtxAny = s.cmdCtx as Record | null; + if (typeof cmdCtxAny?.clearQueue === "function") { + (cmdCtxAny.clearQueue as () => unknown)(); + } + } catch { + // Non-fatal — clearQueue may not be available in all contexts + } + return result; }