Repository navigation
Think: an auto-continuation that fails before streaming is only logged — no onChatError, no response hook, no terminal status #2381
Description
Versions
@cloudflare/think@0.19.0, agents@0.24.0, ai@7.0.113, Workers compatibility date 2026-09-13.
Summary
When the auto-continuation that runs after a client tool-approval response (or tool result) throws before it produces a stream — for example from beforeTurn() or from beforeStep() at step 0 — Think only writes console.error("[Think] Auto-continuation failed:", error). None of the application-facing error paths run:
onChatError()is not called,onChatResponse()is not called (nostatus: "error"result),- no terminal chat status is recorded (
_recordTerminalChatStatus), so nothing is replayed on reconnect, - the connected client only receives
STREAM_RESUME_NONE.
The browser therefore shows no error at all: the approved tool call stays at approval-responded and the chat looks idle. Application resources tied to the turn (leases, reservations released in onChatError) are also left unsettled.
The same kind of failure in a client-submitted turn, or once a continuation is streaming, does reach onChatError / onChatResponse.
Where
dist/think.js, _fireAutoContinuation():
this._admitTurn({
admission: "queue",
trigger: "auto-continuation",
requestId,
continuation: true,
allowNested: true,
execute: async () => {
// ...
try {
// _runInferenceLoop(...) then _streamResult(...) — throws here before any stream
} finally {
this._aborts.remove(requestId);
if (!streamed) this._continuation.sendResumeNone();
this._continuation.clearPending();
this._activateDeferredContinuation();
}
}
}).catch((error) => {
console.error("[Think] Auto-continuation failed:", error); // <- the only handling
this._aborts.remove(requestId);
});Reproduction
-
A
Thinksubclass with an approval-required tool (needsApproval: true, or an inline-approval Action), and abeforeStep()that throws when a flag is set:class ReproAgent extends Think<Env> { failNextStep = false; async beforeStep() { if (this.failNextStep) throw new Error("repro_pre_stream_failure"); } // getTools(): one tool with needsApproval: true }
-
From the client (
useAgentChat), send a message that makes the model call the approval-required tool; the approval card appears. -
Set
failNextStep = trueon the Agent (e.g. via a callable), then approve the tool call (addToolApprovalResponse). -
Think schedules the auto-continuation;
beforeStep()throws at step 0.
Observed: the Worker logs [Think] Auto-continuation failed: Error: repro_pre_stream_failure (plus the Task failure and the Agent's default onError output). onChatError and onChatResponse are never called; the client shows no error and the tool part stays approval-responded. Reconnecting shows nothing either.
Expected: the same handling as a failed client turn — onChatError(error, { stage: "turn", ... }), a terminal chat status recorded for replay, and onChatResponse({ status: "error", continuation: true, ... }) — so applications can settle resources and the client can show the failure.
Workaround we use
We mark continuation turns in beforeTurn() (context.continuation), catch errors thrown from beforeTurn() or from beforeStep() at stepNumber === 0, call our own onChatError() for those, and publish the failure to clients through Agent state. We would like to remove this once Think reports these failures itself.
Related
Because the continuation never runs, the approved call is also never settled; see the companion issue about repairInterruptedToolParts keeping approval-responded parts, which then makes every later turn fail at the provider.
Activity
Momoyeyu commentedon Sep 28, 2026
I can take this. I reproduced the pre-stream failure paths in beforeTurn and beforeStep locally and have a focused fix with Workers regression tests. I will send a PR against main.
fix: report continuation turns that fail before streaming (#2381) (#2393