Repository navigation
fix(server): recover Claude cooldown reset times from proxy errors - #36642
Conversation
Claude Code ignores Anthropic's rate-limit headers when it authenticates with an API key, so a turn rejected by a proxy such as CLIProxyAPI never learns when to resume. Read the reset time from the proxy's cooldown message instead, even when Claude Code labels the turn as rate limited.
# Conflicts: # apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This change recovers proxy cooldown timestamps while avoiding nested upstream reset hints and preserving unknown-limit handling. Its effects are limited to existing usage-limit reporting and recovery timing. You can add or adjust custom eligibility rules. Learn more. |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Other scenariosAll 64 scenario measurements match All 64 scenario measurements
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
…ingdotgg#36642) Co-authored-by: greyxp1 <greyxp999@gmail.com> Co-authored-by: Grey <120752906+greyxp1@users.noreply.github.com> Co-authored-by: maria-rcks <maria@kuuro.net> (cherry picked from commit 487733b)
…5 (Running the fork's tests in apps/server) Failing check: Running the fork's tests in apps/server **What failed:** In "Running the fork's tests in apps/server", three cases in `ClaudeAdapterV2.test.ts` failed: `preserves Switchboard recovery timing for over thirty days`, `for other model` and `for trailing text`. Each expected a reset time and got `null`. **Why:** Those cases are part of the fork's `fork: switchboard` test block. They cover proxy messages that the fork's own parser (`switchboardCooldownResetAt`) rejects on purpose: a cooldown over 30 days, a different model, or text after the reset hint. The fork never read a reset from those messages. It relied on upstream's old catch-all `(reset after …)` match, which found the hint anywhere in a 429 message, and the test comment said so. Upstream's pingdotgg#36642 ("recover Claude cooldown reset times from proxy errors") narrowed that for CLIProxyAPI's "All credentials for model … are cooling down" messages. Now only a cooldown directly after "cooling down" counts, so nested upstream errors are ignored. The fork's message form ("cooling down via provider claude (last error: …) (reset after Ns)") has text in between, so upstream no longer finds a reset there. The fork's parser still rejects them too, so the result is `null`. That is upstream's intended behavior, and upstream's code wins. **What I changed:** Only the fork seam rows in `apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts`. The three rows now expect no reset (`expected: 0`), and the comment above them describes upstream's new reading. I didn't touch the fork's parser or any upstream code, and every other row in the block still asserts what it did before. `docs/fork/seams.tsv` needs no change because the marker count is the same. Rerunning the full `ClaudeAdapterV2.test.ts` gives 449 of 449 passing, and `check_seams` printed nothing.
…ingdotgg#36642) Co-authored-by: greyxp1 <greyxp999@gmail.com> Co-authored-by: Grey <120752906+greyxp1@users.noreply.github.com> Co-authored-by: maria-rcks <maria@kuuro.net> (cherry picked from commit 487733b)
Nothing needed changing. PR #36642 at 1f88524: all 35 checks passed (8 of them required). Macroscope approved it with no line comments. CodeRabbit's review says 'No actionable comments were generated' and rates the merge risk Minimal. No reviewer finding is open. The fix itself is the same as the last round: the adapter reads the CLIProxyAPI cooldown only from the outer 'All credentials … are cooling down (reset after …)' message and ignores nested 'last error' reset hints. It combines that deadline with native Claude rate-limit resets: the latest known reset wins, and an unknown native reset stays unknown.
Supersedes #26735. The change is @greyxp1's; their commits are kept.
Reproduction on main
Reproduced on main at 0fee26d:
All credentials for model claude-opus-5-5 are cooling down (last error: rate_limit_error (reset after 1h)). Running the claude CLI directly against it producedAPI Error: Request rejected (429) · All credentials for model claude-opus-5-5 are cooling down (last error: rate_limit_error (reset after 1h)), exactly the PR's example. 2) Started the main dev server on a fresh database with the Claude provider pointed at the stand-in, and turned on 'Resume after usage limits' in Settings → Threads. 3) Added a demo project and started an Opus 5.5 thread with 'Say hi'. 4) The turn failed at 09:56:43Z. The persisted failure is {class: usage_limit, code: api_error_429, resetAt: 2026-11-14T10:56:43.553Z}, and the thread's limitRecovery has the same resetAt with autoResume: true. The UI shows 'Limited' and 'Usage limit reached · Resumes 10:56 AM'. That one-hour deadline comes only from the nested last-error text; the proxy's own message has no cooldown, so the reset should be unknown. This reproduces the first defect. 5) Second scenario (outer proxy cooldown together with a native rejected rate_limit_event): with the proxy also returning anthropic-ratelimit-unified-* rejected headers, the API-key CLI still emits no rate_limit_event (main's code notes that API-key sessions get no rate_limit_info). Native limit events need first-party Claude OAuth, which no VM has, so the 'later deadline discarded' and 'unknown native reset replaced' cases were not run end to end.Evidence
Run the real Claude CLI through the real server and web client against an HTTP 429 stand-in whose only deadline is nested inside
last error: rate_limit_error (reset after 1h). With automatic recovery enabled, open a fresh Opus 5.5 thread and sendSay hi. Both sides use fresh synthetic state, Chromium on Linux, light theme, and a 1280×800 CSS viewport.Base persisted a one-hour reset deadline and armed automatic recovery. Head persisted
resetAt: nulland no recovery schedule. Both received the same proxy error.Proxy script · Seed script · Reproduction instructions · Observed persisted state
Not shown:
Tests
No new local runs this round, because no code changed. On the PR, CI passed all 35 checks on 1f88524. Last round: vp test run ClaudeAdapterV2.test.ts -t 'recovers wrapped gateway limits' passed 13 of 13, and vp run check:changed passed.
Not in this change
Who did the work
Run by the t3code factory in T3 Code.