Reviewed issue and related code files.
UnderstandTraced error flow in compaction module.
UnderstandImplemented error handling and reporting fix.
BuildUpdated event types, UI, docs, and changelog.
EditConfirmed fix resolves crash and improves error visibility.
VerifyAnalyze GitHub issue(s): https://github.com/badlogic/pi-mono/issues/792
For each issue:
Read the issue in full, including all comments and linked issues/PRs.
For bugs:
For feature requests:
Do NOT implement unless explicitly asked. Analyze and propose only.
The user wants me to analyze GitHub issue #792. Let me fetch the issue details first.
>gh issue view 792 --json title,body,comments,labels,state --repo badlogic/pi-monoThis is a bug report about a crash during Codex compaction when the user runs out of quota. The error shows:
context_length_exceeded from CodexThe issue is in agent-session.js around line 1229, in the _runAutoCompaction function. The error handling seems to be throwing when summarization fails due to quota limits.
Let me read the relevant code files to understand the actual code path and root cause.
Let me read the relevant code files to understand the issue:
Let me continue reading the file to see the rest of the _runAutoCompaction method and related error handling code.
Now I have a clear picture of the issue. Let me trace the code path:
gpt-5.2-codex modelcontext_length_exceeded error_handleAgentEvent, on agent_end, _checkCompaction is called with the last assistant message_checkCompaction detects a context overflow via isContextOverflow() and calls _runAutoCompaction("overflow", true)_runAutoCompaction, compact() is called to summarize the contextcompact() function internally uses the current model to generate a summarycatch block around line 1550:} catch (error) { this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
if (reason === "overflow") { throw new Error( `Context overflow: ${error instanceof Error ? error.message : "compaction failed"}. Your input may be too large for the context window.`, ); }}The problem is that when auto-compaction is triggered by overflow and the compaction itself fails (e.g., due to quota limits), the error is re-thrown and crashes the application. The error message is also confusing because it says "Your input may be too large for the context window" when the real issue is quota exhaustion.
The actual root cause is that the _runAutoCompaction method throws an error when compaction fails during overflow handling, and this error bubbles up and crashes the application since it's not caught anywhere.
Let me check if there's any error handling in the parent context.
Looking at where _checkCompaction is called in _handleAgentEvent, the async event handler is subscribed directly to the agent. If _checkCompaction throws during overflow recovery, that error propagates up without being caught. I should also examine what errors the compact() function itself might throw to understand the full failure chain.
Let me also check the compaction module to understand what errors can be thrown:
Let me read the compaction.ts file to see what errors can be thrown.
Now I have a complete picture:1. The compact() function calls generateSummary() or generateTurnPrefixSummary()
2. These functions use completeSimple() to call the LLM3. If the LLM returns stopReason === "error", the function throws: throw new Error(Summarization failed: ${response.errorMessage || "Unknown error"});4. In _runAutoCompaction, this error is caught, and if the reason is "overflow", it's re-thrown with a confusing message
The issue is that when auto-compaction is triggered due to overflow and the summarization fails (for ANY reason, including quota limits), the error is re-thrown and crashes the entire application.
Root Cause: The _runAutoCompaction method in agent-session.ts throws an unhandled error when compaction fails during overflow handling. The error message is misleading because it says "Your input may be too large for the context window" when the actual issue could be something entirely different (like quota exhaustion).
Proposed Fix: Instead of crashing when auto-compaction fails during overflow:
Looking at the code:
_runAutoCompaction:throw new Error( `Context overflow: ${error instanceof Error ? error.message : "compaction failed"}. Your input may be too large for the context window.`,);The fix should:
Let me check how errors are handled elsewhere in the codebase for consistency.
Now I have a complete picture of the issue. Let me summarize:
The application crashes during Codex compaction when the user runs out of ChatGPT quota.
gpt-5.2-codex modelcontext_length_exceeded error_checkCompaction() detects the overflow and calls _runAutoCompaction("overflow", true)completeSimple() (which uses the current model)_runAutoCompaction catch block → re-thrown with misleading message → application crashesLocation: packages/coding-agent/src/core/agent-session.ts, lines 1546-1552
} catch (error) { this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
if (reason === "overflow") { throw new Error( `Context overflow: ${error instanceof Error ? error.message : "compaction failed"}. Your input may be too large for the context window.`, ); }}Problems:
_handleAgentEvent) causes an unhandled rejection that crashes the applicationModify _runAutoCompaction to NOT throw on compaction failure. Instead:
Changes needed in agent-session.ts:
} catch (error) { const errorMessage = error instanceof Error ? error.message : "compaction failed"; this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
// Don't throw - emit the error for UI to display // The session remains usable (user can switch models, wait for quota, etc.) if (this._extensionRunner) { this._extensionRunner.emitError({ extensionPath: "auto_compaction", event: "compact", error: reason === "overflow" ? `Context overflow recovery failed: ${errorMessage}` : `Auto-compaction failed: ${errorMessage}`, }); } // For overflow case, the error message was already shown to user // Don't crash - let user decide what to do (switch model, wait, etc.)}packages/coding-agent/src/core/agent-session.ts (main fix)compaction_error if more structured error handling is neededthis._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
// Don't throw - emit the error for UI to display // The session remains usable (user can switch models, wait for quota, etc.) if (this._extensionRunner) { this._extensionRunner.emitError({ extensionPath: "auto_compaction", event: "compact", error: reason === "overflow" ? `Context overflow recovery failed: ${errorMessage}` : `Auto-compaction failed: ${errorMessage}`, }); }cehck how @packages/coding-agent/src/modes/interactive/interactive-mode.ts handles the event. are you ssure it does so via extensionRunner? and not via the _emit?
The user is questioning my proposed fix. They want me to check how interactive-mode.ts handles errors and whether it uses extensionRunner or the _emit pattern. Let me read the interactive-mode.ts file to understand the error handling pattern.
Good point, let me check how interactive-mode handles these events:
>grep -n "auto_compaction\|compaction_error\|emitError" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src/modes/interactive/interactive-mode.ts | head -50Let me read those sections to see how interactive-mode handles these events.
I see. The auto_compaction_end event is handled via _emit (which goes through the subscribe mechanism), not via extensionRunner. The event handler shows status if aborted, shows the result if successful, but there's NO handling for the case where event.result is undefined and event.aborted is false (which is what happens when compaction fails with an error).
Looking at the current code:
if (event.aborted) { this.showStatus("Auto-compaction cancelled");} else if (event.result) { // ... show success}// No else case for error!So if compaction fails (result is undefined, aborted is false), nothing is displayed to the user - it just silently fails, then the error is thrown and crashes.
The fix should either:
error field to the auto_compaction_end eventOption 1 seems cleaner since it's just extending the existing event. Let me check the event type definition.
You're right. The auto_compaction_end event is handled via _emit/subscribe, not extensionRunner. Let me check the event type definition:
>grep -n "auto_compaction_end\|AgentSessionEvent" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src/core/agent-session.ts | head -20Now I see the issue clearly. The auto_compaction_end event type is:
{ type: "auto_compaction_end"; result: CompactionResult | undefined; aborted: boolean; willRetry: boolean }It has no error or errorMessage field. When compaction fails:
result is undefinedaborted is falseAnd in interactive-mode.ts, the handling is:
if (event.aborted) { this.showStatus("Auto-compaction cancelled");} else if (event.result) { // ... show success}// Missing: else case for error!So the proper fix is:
errorMessage field to the auto_compaction_end event_runAutoCompaction catch block, emit the event WITH the error message (and don't throw)This is cleaner than using extensionRunner.emitError because:
Now I see the real issue. The auto_compaction_end event has no errorMessage field, and interactive-mode doesn't handle the "failed but not aborted" case:
// Current handling in interactive-mode.ts:if (event.aborted) { this.showStatus("Auto-compaction cancelled");} else if (event.result) { // ... show success}// Missing: else case for error!1. Extend the event type (agent-session.ts line 65):
| { type: "auto_compaction_end"; result: CompactionResult | undefined; aborted: boolean; willRetry: boolean; errorMessage?: string }2. Emit error in catch block (agent-session.ts ~line 1546):
} catch (error) { const errorMessage = error instanceof Error ? error.message : "compaction failed"; this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false, errorMessage: reason === "overflow" ? `Context overflow recovery failed: ${errorMessage}` : `Auto-compaction failed: ${errorMessage}`, }); // Don't throw - let UI handle the error display}3. Handle error in interactive-mode.ts (~line 1815, after the else if (event.result) block):
} else if (event.errorMessage) { this.showStatus(event.errorMessage, "error");}This follows the existing event pattern and gives the user a clear error message without crashing.
ok, does this have an influence on @packages/coding-agent/src/modes/rpc/rpc-mode.ts as well?
The user wants me to check if rpc-mode.ts also handles auto_compaction_end events and needs to be updated.
>grep -n "auto_compaction" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src/modes/rpc/rpc-mode.tsLet me check what events rpc-mode handles and if it subscribes to session events at all.
>grep -n "subscribe\|_emit\|AgentSessionEvent" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src/modes/rpc/rpc-mode.ts | head -30Let me see what rpc-mode does with the subscribed events.
I see! In rpc-mode, events are simply passed through directly to the output:
session.subscribe((event) => { output(event);});So all AgentSessionEvent objects (including auto_compaction_end) are serialized and sent to the RPC client. Adding an errorMessage field to the event type will automatically be included in the output. The RPC client is responsible for handling these events.
This means:
errorMessage field when presentHowever, since this is a change to the RPC protocol (adding a new optional field), we should check if there's any documentation or types that RPC clients use.
RPC mode just passes through all events directly:
session.subscribe((event) => { output(event);});So:
errorMessage field will automatically be included in the JSON outputThe fix only needs changes in:
agent-session.ts - extend event type + emit with errorMessageinteractive-mode.ts - display the error to useralright, please implement the fix
Let me implement the fix. I need to:
auto_compaction_end event type to include an optional errorMessage field_runAutoCompaction to emit the error message instead of throwingLet me start by reading the exact lines I need to modify.
Now let me read the catch block in _runAutoCompaction:
Now let me read the interactive-mode handling to see the exact lines to modify:
Now I have all the context I need. Let me implement the fix:1. Modify the event type in agent-session.ts (line 65)2. Modify the catch block in agent-session.ts (lines 1546-1552) 3. Add error handling in interactive-mode.ts (after line 1822)Let me also check if showStatus has an "error" variant or similar.
>grep -n "showStatus" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src/modes/interactive/interactive-mode.ts | head -10Let me check the showStatus method to see if it supports different styles:
The showStatus method only takes a message and uses theme.fg("dim", message). I could either:
Option 3 is simplest for now. Let me check how error messages are shown elsewhere in interactive-mode for consistency:
>grep -n "theme.fg.*error" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src/modes/interactive/interactive-mode.ts | head -10I see. Errors are shown directly with theme.fg("error", ...). I'll add the error directly in the handler rather than modifying showStatus. Let me now apply all the edits.
Now I have all the context. Let me apply the three edits:
Now let me fix the catch block:
Now let me update interactive-mode.ts to handle the error case:
Now let me run the type check to make sure everything compiles correctly:
Now let me run the type check:
>cd /Users/badlogic/workspaces/pi-mono && npm run check 2>&1All checks pass. Let me also update the changelog for this fix.
All checks pass. Let me update the changelog:
I need to add an entry to the Fixed section. Let me add it.
Done! Let me summarize what was fixed:
Fix implemented. Summary of changes:
packages/coding-agent/src/core/agent-session.ts:
auto_compaction_end event type with optional errorMessage field_runAutoCompaction to emit the error via the event instead of throwingpackages/coding-agent/src/modes/interactive/interactive-mode.ts:
auto_compaction_end handler to display the error message to the userpackages/coding-agent/CHANGELOG.md:
### FixedThe application will no longer crash when auto-compaction fails due to quota limits or other API errors. Instead, the user sees the error message and can take action (switch models, wait for quota reset, etc.).
oki, last check before we commit. any changelog entries missing? docs? if so fix, commit and push
Let me check if there are any other changes needed:
auto_compaction_end event that needs updating?Let me check if there's any documentation about RPC events or the event types.
>grep -r "auto_compaction_end" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/docs/ 2>/dev/null || echo "No docs found">grep -r "auto_compaction_end" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/README.md 2>/dev/null || echo "Not in README"There's documentation in rpc.md about the auto_compaction_end event. I should update it to include the new errorMessage field. Let me read that section.
>grep -n -A 30 "auto_compaction_start / auto_compaction_end" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/docs/rpc.mdI need to update the docs to mention the new errorMessage field. Let me read the exact section to edit.
I need to update this section to document the errorMessage field. Let me edit it.
Now let me run the check again to make sure everything is good, then commit.
>cd /Users/badlogic/workspaces/pi-mono && npm run check 2>&1All checks pass. Now let me verify my changes and commit.
>cd /Users/badlogic/workspaces/pi-mono && git statusAll my changes are there. Let me commit them with a proper message.
>cd /Users/badlogic/workspaces/pi-mono && git add packages/coding-agent/CHANGELOG.md packages/coding-agent/docs/rpc.md packages/coding-agent/src/core/agent-session.ts packages/coding-agent/src/modes/interactive/interactive-mode.ts && git commit -m "fix(coding-agent): handle auto-compaction failures gracefully
When auto-compaction fails (e.g., quota exceeded), emit the error via
the auto_compaction_end event instead of throwing. The UI now displays
the error message, allowing users to take action (switch models, wait
for quota reset, etc.) instead of crashing.
fixes #792"Commit successful. Now push.
>cd /Users/badlogic/workspaces/pi-mono && git pushDone. The issue should be automatically closed since I used "fixes #792" in the commit message.
Done. Pushed commit 20f5fcc7 which:
errorMessage field to auto_compaction_end event typeIssue #792 will be automatically closed.
} catch (error) { this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
if (reason === "overflow") { throw new Error( `Context overflow: ${error instanceof Error ? error.message : "compaction failed"}. Your input may be too large for the context window.`, ); }}throw new Error( `Context overflow: ${error instanceof Error ? error.message : "compaction failed"}. Your input may be too large for the context window.`,);} catch (error) { this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
if (reason === "overflow") { throw new Error( `Context overflow: ${error instanceof Error ? error.message : "compaction failed"}. Your input may be too large for the context window.`, ); }}} catch (error) { const errorMessage = error instanceof Error ? error.message : "compaction failed"; this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
// Don't throw - emit the error for UI to display // The session remains usable (user can switch models, wait for quota, etc.) if (this._extensionRunner) { this._extensionRunner.emitError({ extensionPath: "auto_compaction", event: "compact", error: reason === "overflow" ? `Context overflow recovery failed: ${errorMessage}` : `Auto-compaction failed: ${errorMessage}`, }); } // For overflow case, the error message was already shown to user // Don't crash - let user decide what to do (switch model, wait, etc.)} // Don't throw - emit the error for UI to display // The session remains usable (user can switch models, wait for quota, etc.) if (this._extensionRunner) { this._extensionRunner.emitError({ extensionPath: "auto_compaction", event: "compact", error: reason === "overflow" ? `Context overflow recovery failed: ${errorMessage}` : `Auto-compaction failed: ${errorMessage}`, }); }if (event.aborted) { this.showStatus("Auto-compaction cancelled");} else if (event.result) { // ... show success}// No else case for error!{ type: "auto_compaction_end"; result: CompactionResult | undefined; aborted: boolean; willRetry: boolean }if (event.aborted) { this.showStatus("Auto-compaction cancelled");} else if (event.result) { // ... show success}// Missing: else case for error!// Current handling in interactive-mode.ts:if (event.aborted) { this.showStatus("Auto-compaction cancelled");} else if (event.result) { // ... show success}// Missing: else case for error!| { type: "auto_compaction_end"; result: CompactionResult | undefined; aborted: boolean; willRetry: boolean; errorMessage?: string }} catch (error) { const errorMessage = error instanceof Error ? error.message : "compaction failed"; this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false, errorMessage: reason === "overflow" ? `Context overflow recovery failed: ${errorMessage}` : `Auto-compaction failed: ${errorMessage}`, }); // Don't throw - let UI handle the error display}} else if (event.errorMessage) { this.showStatus(event.errorMessage, "error");}session.subscribe((event) => { output(event);});session.subscribe((event) => { output(event);});