fix: cancel trailing async jobs on session switch to prevent wasted LLM turns (#1643)

When a unit spawns background jobs via async_bash, job completion callbacks
fire follow-up messages after agent_end has resolved. The auto-loop has
moved on but the previous session's LLM processes these follow-ups, adding
12-45s of wasted time and ~14 unnecessary turns per unit.

Two complementary fixes:
1. Cancel all running background jobs on session_before_switch so
   completion callbacks never fire for the old session.
2. Clear the follow-up queue after runUnit() completes as defense-in-depth,
   discarding any already-queued notifications before the next session starts.

Closes #1642

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
TÂCHES 2026-03-20 14:29:38 -06:00 committed by GitHub
parent 2d08391649
commit b124b79a12
2 changed files with 25 additions and 0 deletions

View file

@ -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();

View file

@ -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<string, unknown> | null;
if (typeof cmdCtxAny?.clearQueue === "function") {
(cmdCtxAny.clearQueue as () => unknown)();
}
} catch {
// Non-fatal — clearQueue may not be available in all contexts
}
return result;
}