From d60e0eaccb3d785c915ec2b51dda61f5a5f73ac8 Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Fri, 17 Apr 2026 15:14:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=BA=A2=E5=87=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/query.ts | 12 ++++++++++++ src/services/acp/agent.ts | 1 + src/services/acp/bridge.ts | 9 +++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/query.ts b/src/query.ts index 8bfca6111..2340c0c72 100644 --- a/src/query.ts +++ b/src/query.ts @@ -330,6 +330,11 @@ async function* queryLoop( // sites. let taskBudgetRemaining: number | undefined = undefined + // Guard against stop_hook_blocking infinite loops (see ~line 1326). + // Loop-local to avoid touching the 7 continue sites on State. + const MAX_STOP_HOOK_BLOCKING_RETRIES = 3 + let stopHookBlockingCount = 0 + // Snapshot immutable env/statsig/session state once at entry. See QueryConfig // for what's included and why feature() gates are intentionally excluded. const config = buildQueryConfig() @@ -1324,6 +1329,13 @@ async function* queryLoop( } if (stopHookResult.blockingErrors.length > 0) { + stopHookBlockingCount++ + if (stopHookBlockingCount > MAX_STOP_HOOK_BLOCKING_RETRIES) { + yield createAssistantAPIErrorMessage({ + content: `Stop hook blocked ${stopHookBlockingCount} times — stopping to prevent infinite loop.`, + }) + return { reason: 'completed' } + } const next: State = { messages: [ ...messagesForQuery, diff --git a/src/services/acp/agent.ts b/src/services/acp/agent.ts index 092adfa09..0b48cc19a 100644 --- a/src/services/acp/agent.ts +++ b/src/services/acp/agent.ts @@ -505,6 +505,7 @@ export class AcpAgent implements Agent { includePartialMessages: true, replayUserMessages: true, initialMessages: opts.initialMessages, + maxTurns: 200, } const queryEngine = new QueryEngine(engineConfig) diff --git a/src/services/acp/bridge.ts b/src/services/acp/bridge.ts index edf9102d3..450f64857 100644 --- a/src/services/acp/bridge.ts +++ b/src/services/acp/bridge.ts @@ -573,6 +573,7 @@ export async function forwardSessionUpdates( // Race the next message against the abort signal so we unblock // immediately when cancelled, even if the generator is waiting for // a slow API response. + let abortHandler: (() => void) | undefined const nextResult = await Promise.race([ sdkMessages.next(), new Promise>((resolve) => { @@ -580,10 +581,14 @@ export async function forwardSessionUpdates( resolve({ done: true, value: undefined }) return } - const handler = () => resolve({ done: true, value: undefined }) - abortSignal.addEventListener('abort', handler, { once: true }) + abortHandler = () => resolve({ done: true, value: undefined }) + abortSignal.addEventListener('abort', abortHandler, { once: true }) }), ]) + // Clean up: remove un-fired listener when generator completes first + if (abortHandler) { + abortSignal.removeEventListener('abort', abortHandler) + } if (nextResult.done || abortSignal.aborted) break const msg = nextResult.value