Analyzed GitHub issue and related comments.
UnderstandTraced code path for error handling and context overflow.
UnderstandModified overflow detection to exclude 429 errors.
EditUpdated retry logic and changelog.
EditCommitted, pushed, and commented to close issue.
VerifyAnalyze GitHub issue(s): https://github.com/badlogic/pi-mono/issues/1038 figure out why we auto-compact on 429
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 #1038. Let me start by reading the issue in full.
>gh issue view 1038 --repo badlogic/pi-mono --json title,body,comments,labels,state{"body":"### What happened?\n\nOn a new session, 429 HTTP status triggers auto compaction.\n\nI am using Chutes, their rate limiting (I expect sequential session limits?) are showing up more often in the last week, especially since Grok Code and a few other free models closed off access.\n\n\n\n### Steps to reproduce\n\nUsing chutes, trigger 429 by sending many (in my case, only 3) requests from concurrent pi instances.\n\n```text\n\n pi v0.50.1\n escape to interrupt\n ctrl+c to clear\n ctrl+c twice to exit\n ctrl+d to exit (empty)\n ctrl+z to suspend\n ctrl+k to delete to end\n shift+tab to cycle thinking level\n ctrl+p/shift+ctrl+p to cycle models\n ctrl+l to select model\n ctrl+o to expand tools\n ctrl+t to expand thinking\n ctrl+g for external editor\n / for commands\n ! to run bash\n !! to run bash (no context)\n alt+enter to queue follow-up\n alt+up to edit all queued messages\n ctrl+v to paste image\n drop files to attach\n\n\n\n [compaction]\n\n Compacted from 35 tokens (ctrl+o to expand)\n\n\n\n im on macos, how do i relaunch the spotlight indexing or something? bcs its\n not showing up results when i search for app names in spotlight\n\n\n Error: 429 status code (no body)\n\n\n [compaction]\n\n Compacted from 35 tokens (ctrl+o to expand)\n\n\n Error: 429 status code (no body)\n\n Auto-compaction cancelled\n\n────────────────────────────────────────────────────────────────────────────────\n\n────────────────────────────────────────────────────────────────────────────────\n~\n0.0%/262k (auto) (chutes) moonshotai/Kimi-K2.5-TEE\n\n\n```\n\n### Expected behavior\n\n429 ideally should be retried with a backoff, AFAIK 429 status never means context limit reached right? Alternatively some kind of guardrail that can tell something is amiss if the numbers don't add up - compacting 35 tokens in itself makes no sense at all.\n\n### Version\n\n0.50.1","comments":[{"id":"IC_kwDOPbFNk87jMBu_","author":{"login":"kierr"},"authorAssociation":"NONE","body":"This is the full message:\n\n```text\n Error: 429 status code (no body)\n\n ⠦ Context overflow detected, Auto-compacting... (escape to cancel)\n```\n\nanother variant, it happened mid-session:\n\n```text\n Error: 429 status code (no body)\n\n Context overflow recovery failed: Summarization failed: 429 status code (no\n body)\n\n────────────────────────────────────────────────────────────────────────────────\n\n────────────────────────────────────────────────────────────────────────────────\n~/.pi\n↑48k ↓5.6k R110k 8.7%/262k (auto) (chutes) moonshotai/Kimi-K2.5-TEE\n```\n\nSeems that Chutes is ratelimiting specifically per model ('chute') and K2.5 is extra popular right now.","createdAt":"2026-01-28T14:19:10Z","includesCreatedEdit":true,"isMinimized":false,"minimizedReason":"","reactionGroups":[],"url":"https://github.com/badlogic/pi-mono/issues/1038#issuecomment-3811580863","viewerDidAuthor":false}],"labels":[{"id":"LA_kwDOPbFNk88AAAACHWTBxw","name":"bug","description":"Something isn't working","color":"d73a4a"}],"state":"OPEN","title":"429 status triggers auto compaction"}The issue is that a 429 HTTP status code (rate limiting) is triggering auto-compaction, which is meant for context overflow situations. Let me trace the code to understand why this happens.
The key areas to look at:
Let me search for relevant code in the coding-agent package.
Now let me trace the code path. I'll look at how errors are handled and where context overflow detection occurs.
>rg -l "context overflow" --ignore-case /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src/Users/badlogic/workspaces/pi-mono/packages/coding-agent/src/core/agent-session.ts
/Users/badlogic/workspaces/pi-mono/packages/coding-agent/src/modes/interactive/interactive-mode.ts>rg -l "auto-compact\|auto.compact\|autoCompact" --ignore-case /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src(no output)
Command exited with code 1>rg -l "429\|rate.limit" --ignore-case /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src(no output)
Command exited with code 1Let me look at the agent-session.ts and interactive-mode.ts files to understand the context overflow detection and auto-compaction logic.
I need to continue reading these files to find the relevant code. Let me look at:
isContextOverflow function in the ai package_checkCompaction method more closelyI can see that in agent-session.ts around line 495-523 there's the _checkCompaction method which handles context overflow detection. Let me look at the isContextOverflow function.
Let me look at the isContextOverflow function to understand how context overflow is detected:
>rg -l "isContextOverflow" /Users/badlogic/workspaces/pi-mono/packages/ai/src/Users/badlogic/workspaces/pi-mono/packages/ai/src/utils/overflow.tsLet me read the overflow.ts file to understand how context overflow is detected.
Now I can see the root cause of the issue. Let me trace through:
overflow.ts at lines 91-94:// Cerebras and Mistral return 400/413/429 with no body - check for status code pattern// 429 can indicate token-based rate limiting which correlates with context overflowif (/^4(00|13|29)\s*(status code)?\s*\(no body\)/i.test(message.errorMessage)) { return true;}This is the root cause! The code treats 429 status code (no body) as a context overflow, when in reality 429 is a rate limit error, not a context overflow error.
The comment even says "429 can indicate token-based rate limiting which correlates with context overflow" but this is wrong:
The problem: Some providers like Cerebras and Mistral return 400/413/429 with empty body for various errors including context overflow. But 429 is primarily a rate limit error, not a context overflow error.
The fix:
Let me also check what happens after isContextOverflow is detected in agent-session.ts to confirm the full flow.
Looking at _checkCompaction in agent-session.ts (lines 495-523):
// Skip overflow check if the message came from a different model.// Skip overflow check if the error is from before a compaction in the current path.When the overflow check passes, the code removes the assistant's error message and triggers auto-compaction. This means 429 rate limit errors are incorrectly triggering message compaction instead of being handled with exponential backoff. The flow incorrectly assumes any 4xx status code without a body indicates context overflow, when 429 specifically signals rate limiting and should be retried, not compacted.
Let me check the retry error handling logic:
>rg "_isRetryableError|_handleRetryableError" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src/core/agent-session.ts -A 5if (this._isRetryableError(msg)) {
const didRetry = await this._handleRetryableError(msg);
if (didRetry) return; // Retry was initiated, don't proceed to compaction
} else if (this._retryAttempt > 0) {
// Previous retry succeeded - emit success event and reset counter
this._emit({
type: "auto_retry_end",
--
private _isRetryableError(message: AssistantMessage): boolean {
if (message.stopReason !== "error" || !message.errorMessage) return false;
// Context overflow is handled by compaction, not retry
const contextWindow = this.model?.contextWindow ?? 0;
if (isContextOverflow(message, contextWindow)) return false;
--
private async _handleRetryableError(message: AssistantMessage): Promise<boolean> {
const settings = this.settingsManager.getRetrySettings();
if (!settings.enabled) return false;
this._retryAttempt++;Now I see the full picture:1. In _handleAgentEvent, after agent_end: - First, check if _isRetryableError() returns true - If it does, call _handleRetryableError() which does the retry with backoff - If not, check for context overflow and auto-compact2. The issue is in _isRetryableError(): typescript private _isRetryableError(message: AssistantMessage): boolean { if (message.stopReason !== "error" || !message.errorMessage) return false; // Context overflow is handled by compaction, not retry const contextWindow = this.model?.contextWindow ?? 0; if (isContextOverflow(message, contextWindow)) return false; It explicitly skips retry if isContextOverflow() returns true.3. Since isContextOverflow() treats 429 as context overflow, 429 errors: - Are NOT retried (because _isRetryableError() returns false)
isContextOverflow() returns true in _checkCompaction())The fix is simple: Remove 429 from the context overflow detection regex.429 should NEVER be treated as context overflow because:_isRetryableError method to see what errors ARE retried:Let me read the full retry error detection logic:
Let me continue to find the _isRetryableError method:
>rg -n "_isRetryableError" /Users/badlogic/workspaces/pi-mono/packages/coding-agent/src/core/agent-session.ts366: if (this._isRetryableError(msg)) {
1875: private _isRetryableError(message: AssistantMessage): boolean {Let me read the _isRetryableError method in full:
Now I understand the full picture. The bug is clear:
In packages/ai/src/utils/overflow.ts, line 91-94:
// Cerebras and Mistral return 400/413/429 with no body - check for status code pattern// 429 can indicate token-based rate limiting which correlates with context overflowif (/^4(00|13|29)\s*(status code)?\s*\(no body\)/i.test(message.errorMessage)) { return true;}The code incorrectly treats 429 (Too Many Requests) as a context overflow error. The rationale in the comment ("token-based rate limiting correlates with context overflow") is flawed:
These are completely different concepts. 429 means you're making too many requests too quickly, not that your request is too large.
isContextOverflow() returns true because of the regex matching "429 status code (no body)"_isRetryableError() returns false (because isContextOverflow() returned true)_checkCompaction() triggers auto-compaction because isContextOverflow() is trueRemove 429 from the context overflow detection regex. 429 should:
The corrected regex should be:
if (/^4(00|13)\s*(status code)?\s*\(no body\)/i.test(message.errorMessage)) { return true;}This keeps 400 (Bad Request) and 413 (Payload Too Large) as potential context overflow indicators while removing 429. The _isRetryableError() function already handles 429 properly with its rate limit detection pattern, so fixing isContextOverflow() to exclude 429 allows the retry logic to work as intended instead of triggering unnecessary compaction.
429 HTTP status code (rate limiting) incorrectly triggers auto-compaction instead of retry with backoff.
File: packages/ai/src/utils/overflow.ts, lines 91-94
// Cerebras and Mistral return 400/413/429 with no body - check for status code pattern// 429 can indicate token-based rate limiting which correlates with context overflowif (/^4(00|13|29)\s*(status code)?\s*\(no body\)/i.test(message.errorMessage)) { return true;}The code treats 429 status code (no body) as context overflow. The comment's reasoning is flawed:
These are completely different concepts. 429 = "Too Many Requests" = rate limiting, NOT context overflow.
429 status code (no body) (rate limited)isContextOverflow() returns true (matches regex)_isRetryableError() in agent-session.ts:1879 returns false:
if (isContextOverflow(message, contextWindow)) return false;_checkCompaction() triggers auto-compaction because isContextOverflow() is trueChange in packages/ai/src/utils/overflow.ts:
// Before:if (/^4(00|13|29)\s*(status code)?\s*\(no body\)/i.test(message.errorMessage)) {
// After:if (/^4(00|13)\s*(status code)?\s*\(no body\)/i.test(message.errorMessage)) {Keep:
Remove:
The retry logic already handles 429 correctly. In agent-session.ts:1886-1888:
return /overloaded|rate.?limit|too many requests|429|500|502|503|504|.../i.test(err);Once isContextOverflow() stops returning true for 429, _isRetryableError() will correctly return true, and 429 errors will be retried with exponential backoff as expected.
packages/ai/src/utils/overflow.ts - Remove 429 from overflow regexpackages/ai/CHANGELOG.md - Document the fixdo it
The user wants me to implement the fix. Let me:
npm run check to verifyNow I have both files. Let me make the edits:
/^4(00|13|29) to /^4(00|13) and update the commentLet me make these changes.
Now let me run npm run check to verify everything is correct.
>cd /Users/badlogic/workspaces/pi-mono && npm run check 2>&1> pi-monorepo@0.0.3 check
> biome check --write . && tsgo --noEmit && cd packages/web-ui && npm run check
Checked 442 files in 275ms. No fixes applied.
> @mariozechner/pi-web-ui@0.50.1 check
> biome check --write . && tsc --noEmit && cd example && biome check --write . && tsc --noEmit
Checked 73 files in 38ms. No fixes applied.
Checked 3 files in 7ms. No fixes applied.All checks pass. Let me verify my changes by reading the modified file.
Done. Changes made:
packages/ai/src/utils/overflow.ts:
/^4(00|13|29) to /^4(00|13) to exclude 429 from context overflow detectionpackages/ai/CHANGELOG.md:
[Unreleased]The fix ensures 429 errors are now correctly handled by the retry logic (exponential backoff) in agent-session.ts instead of triggering auto-compaction. The existing retry regex already matches 429:
/overloaded|rate.?limit|too many requests|429|500|502|503|504|.../commit and push, leave a comment explaining the fix. close issue in commit message
The user wants me to commit the changes, push, and leave a comment on the issue explaining the fix. I should close the issue in the commit message using "fixes #1038".
>cd /Users/badlogic/workspaces/pi-mono && git add packages/ai/src/utils/overflow.ts packages/ai/CHANGELOG.md && git commit -m "fix(ai): 429 rate limit errors no longer trigger auto-compaction
429 (Too Many Requests) was incorrectly classified as context overflow,
triggering compaction instead of retry with backoff. The original logic
assumed token-based rate limiting correlates with context overflow, but
these are different concepts:
- Rate limiting (429): requests/tokens per time period (throughput)
- Context overflow: single request exceeds context window (size)
Now 429 errors are handled by the existing retry logic with exponential
backoff, while 400/413 remain as potential context overflow indicators.
fixes #1038"Running formatting, linting, and type checking...
> pi-monorepo@0.0.3 check
> biome check --write . && tsgo --noEmit && cd packages/web-ui && npm run check
Checked 442 files in 280ms. No fixes applied.
> @mariozechner/pi-web-ui@0.50.1 check
> biome check --write . && tsc --noEmit && cd example && biome check --write . && tsc --noEmit
Checked 73 files in 37ms. No fixes applied.
Checked 3 files in 8ms. No fixes applied.
✅ All pre-commit checks passed!
[main 25707f9a] fix(ai): 429 rate limit errors no longer trigger auto-compaction
2 files changed, 8 insertions(+), 7 deletions(-)Good, commit succeeded. Now push and add a comment to the issue.
>cd /Users/badlogic/workspaces/pi-mono && git pushTo https://github.com/badlogic/pi-mono.git
a373dce7..25707f9a main -> main>gh issue comment 1038 --repo badlogic/pi-mono --body "Fixed in 25707f9a.
**Root cause:** The \`isContextOverflow()\` function in \`packages/ai/src/utils/overflow.ts\` was matching \`429 status code (no body)\` as a context overflow error. This caused 429 rate limit responses to trigger auto-compaction instead of the retry logic.
**The fix:** Removed 429 from the overflow detection regex. Now:
- **400/413** (Bad Request / Payload Too Large) → still treated as potential context overflow → triggers compaction
- **429** (Too Many Requests) → rate limiting → triggers retry with exponential backoff
The retry logic in \`agent-session.ts\` already handles 429 correctly, it just wasn't being reached because \`isContextOverflow()\` was returning true first."https://github.com/badlogic/pi-mono/issues/1038#issuecomment-3814519855Done. Pushed and commented on the issue. The issue will be closed automatically because the commit message contains "fixes #1038".
Done. Committed, pushed, and commented on the issue. The issue will be auto-closed by GitHub due to fixes #1038 in the commit message.
// Cerebras and Mistral return 400/413/429 with no body - check for status code pattern// 429 can indicate token-based rate limiting which correlates with context overflowif (/^4(00|13|29)\s*(status code)?\s*\(no body\)/i.test(message.errorMessage)) { return true;}// Skip overflow check if the message came from a different model.// Skip overflow check if the error is from before a compaction in the current path.// Cerebras and Mistral return 400/413/429 with no body - check for status code pattern// 429 can indicate token-based rate limiting which correlates with context overflowif (/^4(00|13|29)\s*(status code)?\s*\(no body\)/i.test(message.errorMessage)) { return true;}if (/^4(00|13)\s*(status code)?\s*\(no body\)/i.test(message.errorMessage)) { return true;}// Cerebras and Mistral return 400/413/429 with no body - check for status code pattern// 429 can indicate token-based rate limiting which correlates with context overflowif (/^4(00|13|29)\s*(status code)?\s*\(no body\)/i.test(message.errorMessage)) { return true;}if (isContextOverflow(message, contextWindow)) return false;// Before:if (/^4(00|13|29)\s*(status code)?\s*\(no body\)/i.test(message.errorMessage)) {
// After:if (/^4(00|13)\s*(status code)?\s*\(no body\)/i.test(message.errorMessage)) {return /overloaded|rate.?limit|too many requests|429|500|502|503|504|.../i.test(err);/overloaded|rate.?limit|too many requests|429|500|502|503|504|.../