Skip to content

fix(server): Claude threads continue after an account hits its limit - #275

Merged
bk-agent-01 merged 2 commits into
bkmainfrom
fix/claude-limit-auto-continue
Oct 6, 2026
Merged

bk-agent-01 merged 2 commits into
bkmainfrom
fix/claude-limit-auto-continue

Conversation

@bk-agent-01

@bk-agent-01 bk-agent-01 commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

When a Claude account reached its usage limit, the chat stopped with "Claude usage limit reached". The account rotation code is supposed to move an Auto thread to the next account and send "Continue". It never ran.

The cause: the rotation listened only for account.rate-limits.updated. When Claude rejects a request, its rate_limit_event has no top-level utilization (only unifiedWindows.<window>.utilization), so claudeRateLimitEventToUpdate drops it and that event never fires. Model-scoped rejections (seven_day_opus, …) never produce it at all.

Evidence from bkt3: about 20 rejected events in the provider logs since 2026-10-03, for example thread aee72ad8… at 2026-10-05 20:28 UTC. Each produced only the usage-limit runtime.warning. The server log has 55 claude.account.placed lines since the last restart and zero claude.account.hard-limit lines.

Fix

  1. claudeHardLimitRotation.expbkt3.ts: trigger on the adapter's usage-limit runtime.warning. It carries Claude's rate_limit_info as its detail, covers every limit type, and fires only when the rejection blocks the turn. A rejection that overage absorbs stays quiet, as it does today. No upstream file changes.
  2. ClaudeAccountsService.ts: when the limit resets in 5 minutes or less, an Auto thread does not move. The session stops, and a notice says when the thread will continue. One minute after the reset, the thread gets a "Continue" turn on the same account.
    • A message accepted for the thread, an account-mode change, or a thread delete cancels the wait.
    • If Claude still refuses after the reset time, the thread moves instead of waiting again.
    • The timer is in memory, so a server restart during the wait leaves only the notice.

Pinned threads are not changed: they still get a notice only.

Tests

  • claudeHardLimitRotation.expbkt3.test.ts: the warning triggers rotation for 5-hour and Opus limits. A rejection that overage absorbs, other warnings, and allowed events do not trigger it.
  • ClaudeAccountsService.test.ts: the thread waits and then continues on the same account; a later refusal with the old reset moves it; an accepted message, a turn start, or a mode change cancels the wait; a reset more than 5 minutes away moves the thread.
  • Locally: 84 tests in the 3 related files pass, the server typecheck passes, lint is clean on the changed files, and the fork-marker check passes against upstream main.

Known follow-up

If the switcher's cached usage still shows 100% after the reset, the continue turn lands on another account. The thread still continues, but it loses the cache the wait tried to keep.

Deploy note

Merging to bkmain restarts bkt3 and stops every running session on it. This PR targets bkmain directly, at the operator's request (no expbkt3 stage).

Opus 5.5 in Claude Code.

🤖 Generated with Claude Code

A rejected rate_limit_event carries its utilization only under
unifiedWindows, so claudeRateLimitEventToUpdate dropped it and no
account.rate-limits.updated event fired. The hard-limit rotation never
ran: Auto threads stopped with the usage-limit row instead of moving to
the next account and continuing.

Read the fraction from unifiedWindows when the top-level one is missing,
and accept seven_day_overage_included in the rotation listener.

An Auto thread whose window resets within five minutes now keeps its
account: the session stops, a notice names the continue time, and the
thread continues on the same account a minute after the reset. A user
message, an account-mode change or a thread delete cancels the wait.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Oct 6, 2026
Review findings on the first commit:

- The converter fix still dropped model-scoped and overage rejections,
  and it let rejections that overage absorbs stop the thread. Trigger the
  rotation from the adapter's usage-limit runtime.warning instead: it
  carries rate_limit_info, covers every limit type, and fires only when
  the rejection blocks the turn. The upstream converter is unchanged.
- Clear the dedup key when a wait ends, so a fresh rejection on the
  same account is handled instead of swallowed.
- Only wait while the reset is still ahead; a rejection whose reset
  has passed moves the thread instead of waiting again.
- Cancel the wait as soon as a message is accepted for the thread
  (thread.turn-start-requested), not only once its turn starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the size:L label Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire — 13.7 KiB — 15.1 KiB ✅
Codex Thread snapshot wire — 7.2 KiB — 7.8 KiB ✅
Codex Live turn WebSocket wire — 6.5 KiB — 7.8 KiB ✅
Codex Live turn WebSocket decoded — 56.3 KiB — 66.4 KiB ✅
Codex Live turn messages — 9 — 21 ✅
Claude Total thread wire — 13.7 KiB — 15.1 KiB ✅
Claude Thread snapshot wire — 7.2 KiB — 7.8 KiB ✅
Claude Live turn WebSocket wire — 6.5 KiB — 7.8 KiB ✅
Claude Live turn WebSocket decoded — 57.1 KiB — 66.4 KiB ✅
Claude Live turn messages — 9 — 21 ✅

Baseline: unavailable · PR result: f3f2671 · Source CI: success

Scenario and decoded snapshot size

10 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.

  • Codex decoded thread snapshot: 114.6 KiB
  • Claude decoded thread snapshot: 115.3 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@bk-agent-01
bk-agent-01 merged commit 7f8bd50 into bkmain Oct 6, 2026
19 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant