From 91d629d6c6a77508ac1be2b16e6dc8297d9692f0 Mon Sep 17 00:00:00 2001 From: tbounsiar Date: Tue, 24 Mar 2026 22:19:35 +0100 Subject: [PATCH] fix: prevent prompt loop hang when cancel races with first result (#458) Fixes #442 ## Summary - When `cancel()` was called immediately after a prompt, the result message arrived before `promptReplayed` was set to `true` - The `!promptReplayed` check consumed it as a "background task result", making the `session.cancelled` check unreachable - The loop then blocked forever on `query.next()` since no further messages would arrive - Fix: move the `session.cancelled` check **before** the `!promptReplayed` check so cancellation is always detected ## Test plan - [x] `npm run build` compiles clean - [x] `npm run lint` passes - [x] All unit tests pass - [x] Logic verified against the race condition sequence described in #442 --- src/acp-agent.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/acp-agent.ts b/src/acp-agent.ts index 992d3ee8..3cd3ba1d 100644 --- a/src/acp-agent.ts +++ b/src/acp-agent.ts @@ -586,6 +586,14 @@ export class ClaudeAcpAgent implements Agent { }); } + // Check cancelled before promptReplayed — when a cancel races + // with the first result, promptReplayed is still false and the + // result would be consumed as a background task, blocking the + // loop forever (see #442). + if (session.cancelled) { + return { stopReason: "cancelled" }; + } + if (!promptReplayed) { // This result is from a background task that finished after // the previous prompt loop ended. Consume it and continue @@ -594,10 +602,6 @@ export class ClaudeAcpAgent implements Agent { break; } - if (session.cancelled) { - return { stopReason: "cancelled" }; - } - // Build the usage response const usage: PromptResponse["usage"] = { inputTokens: session.accumulatedUsage.inputTokens,