You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Think recovery bypasses onChatError for beforeTurn failures and replaces the original error #2551
An application error thrown by beforeTurn is handled differently depending on whether the turn is a normal WebSocket request or a recovered turn. Recovery bypasses onChatError, emits onExhausted with reason: "recovery_error", and replaces the submission's original error with the configured terminalMessage.
This loses both the application's user-facing error mapping and the cause needed to classify the failed submission. The original error is only logged to the console.
To reproduce
Override beforeTurn to throw new Error("AccessDenied"), and onChatError to return new Error("Please sign in again."). Set chatRecovery.terminalMessage to "Recovery stopped.".
Run a normal WebSocket turn, then a recovery continuation and a recovery retry on separate instances. Reproduced with real SQLite-backed Durable Objects in Miniflare; no model or external service is needed:
Path
onChatError calls
Durable terminal message
Submission error
Normal WebSocket turn
1
Please sign in again.
n/a
Recovery continue
0
Recovery stopped.
Recovery stopped.
Recovery retry
0
Recovery stopped.
Recovery stopped.
Both recovery cases emit onExhausted on attempt 1, with attempts still available.
Standalone reproduction
The probe seeds an interrupted submission and calls the recovery callback body directly to make the failure deterministic without forcing an isolate eviction. The ordinary WebSocket path is the control case.
In an empty directory, create package.json with { "private": true, "type": "module" }, then install:
Run node run.mjs. Results are printed and saved to result.json.
Expected behavior
Application errors during a recovered turn should go through the existing onChatError hook, preserving its mapped terminal message for clients and reconnects. The underlying cause should remain available on the failed submission. The turn must still terminate; platform-transient retries and actual recovery-budget exhaustion should keep their existing behavior.
Related: #1659 added this terminalization path to prevent a thrown recovery callback from leaving the turn stuck. That guarantee is necessary; the remaining issue is preserving the application's error handling when terminalizing.
Activity