[BUG] Parallel Claude Code sessions started right after 5-hour limit resets — first 3–4 work, the rest fail with "Server is temporarily limiting requests (not your usage limit) · Rate limited"

Status Closed — not planned
Reported on v2.1.119
Maintainer reply None cached
Activity 11 comments · opened Apr 27, 2026 · closed Jul 5, 2026

Preflight Checklist

  • [x] I have searched existing issues and this specific scenario hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code (2.1.119)

What's Wrong?

When my 5-hour usage window resets and I resume work by bulk-spawning ~10
Claude Code sessions back-to-back via a script
(each running in its own
terminal / git worktree for parallel spec implementation), the first 3–4
sessions start normally, but the next 5–6 sessions fail almost immediately
with:

API Error: Server is temporarily limiting requests (not your usage limit) · Rate limited

The error message itself states it is not the user's usage limit — yet it
consistently triggers only on the later sessions of a rapid-fire parallel
launch right after a quota reset. Waiting and retrying the failed sessions
individually eventually succeeds, which strongly suggests a server-side
concurrency/burst limiter that activates aggressively when many sessions
bootstrap simultaneously.

This is reproducible nearly every single time I follow my normal workflow
(a single command that fans out parallel spec implementation across multiple
worktrees), so this isn't a one-off transient.

What Should Happen?

Either:

  1. All sessions should be admitted (the message claims it's not a usage-limit

issue), or

  1. The CLI should transparently back off and retry the bootstrap requests

with jitter instead of surfacing a hard error to the user, or

  1. The error message should clearly state that this is a concurrency/burst

limit and document how many parallel session bootstraps are supported per
unit of time.

Steps to Reproduce

  1. Hit the 5-hour usage limit and wait for it to reset.
  2. Run a script / wrapper command that spawns ~10 \claude\ processes in rapid

succession across separate working directories (git worktrees in my case).

  1. Observe: the first 3–4 sessions initialize and work normally.
  2. The next 5–6 sessions return

\API Error: Server is temporarily limiting requests (not your usage limit) · Rate limited\
shortly after startup or on their first prompt.

  1. Retrying the failed sessions one-by-one with a delay eventually succeeds.

Error Messages/Logs

\\\
API Error: Server is temporarily limiting requests (not your usage limit) · Rate limited
\
\\

Claude Model

Opus 4.7 (claude-opus-4-7, 1M context) — same behavior also observed when
mixed with Sonnet sessions.

Is this a regression?

Not sure — this pattern has been happening for several weeks across multiple
Claude Code versions on the same plan.

Claude Code Version

2.1.119

Platform

Claude Max plan (OAuth, not direct API key)

Operating System

Linux (Ubuntu 22.04, kernel 6.8.0-110-generic)

Terminal/Shell

bash, multiple GNOME Terminal windows launched by a single fan-out command

Additional Information

  • The defining trigger is **(a) right after a 5-hour reset, (b) on Linux,

(c) bulk-spawning ~10 sessions back-to-back from a single command, where
the later 5–6 reliably fail while the first 3–4 succeed**.

  • Related but distinct: #37436 (Windows, MAX100, multi-session quota

consumption), #40273 (rate limiter when spawning >1 agent), #44481 (Agent
Teams 429/529 with concurrent teammates), #53915 / #53531 (same error
message but different trigger). Filing separately because the existing
issues either don't reproduce on Linux or describe a different trigger.

  • This is mildly disruptive rather than fully blocking — failed sessions can

be retried after a delay — but it consistently breaks the "fan out to ~10
parallel worktrees right after a reset" workflow that I rely on for
parallel spec implementation.

  • Suggested mitigation on the CLI side: stagger session bootstrap requests

with backoff+jitter, or detect this specific server response and queue the
retry transparently instead of failing the session.

View original on GitHub ↗

11 Comments

github-actions[bot] · 4 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/40273
  2. https://github.com/anthropics/claude-code/issues/44481
  3. https://github.com/anthropics/claude-code/issues/53915

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

Manuelreyesbravo · 3 months ago

Concurrent Session Throttling on Max Plans — Architectural Analysis & Fix Proposal

Reproducing the same pattern reported in #53922, #46037, #38335, #41788, #54750, #8449. After eliminating every plausible client-side cause, the bottleneck is per-account concurrent-stream throttling that does not scale with the plan multiplier. Sharing evidence and concrete fixes.

---

Reproduction

| Field | Value |
|---|---|
| Plan | Max 20x (recently upgraded from Max 5x) |
| Client | Claude Code 2.1.64 (claude-desktop 1.1.5749) |
| Model | claude-opus-4-7[1m] |
| OS | Linux Fedora 43 |

Behavior:

  • Morning of upgrade to 20x: N parallel Opus sessions worked perfectly.
  • Same afternoon onwards: launching 3–4 Opus sessions in parallel → only 1 advances; the others freeze indefinitely on "thinking" with no error surfaced.
  • Sonnet 4.6 in the same parallel scenario works without issues.
  • Mixing 1 Opus + 2–3 Sonnet works.
  • Quota dashboard shows usage far below the cap throughout.

This matches #53922 verbatim ("first 3–4 work, the rest fail") and #46037 ("only 1 session works while quota is unused").

---

Evidence ruling out client / local causes

I rebuilt the local stack to eliminate every plausible client-side bottleneck before concluding it's server-side:

  1. MCP server (118 tools): SQLite migrated to WAL + busy_timeout=30000, dispatch handlers wrapped with asyncio.to_thread for blocking ops. Local benchmark of 4 concurrent tool calls: 66ms → 62ms (1.07× speedup) — concurrency works at the MCP layer.
  2. MCP transport: tested both stdio and Streamable HTTP via StreamableHTTPSessionManager. Both connect successfully ([LocalMcpServerManager] Connected to mimir (118 tools)). Not a transport issue.
  3. Process layer: replaced uv run with direct .venv/bin/python3 to remove uv's global cache lock. Multi-PID lock file. No single-instance enforcement.
  4. DB lock contention: 4 processes × 50 concurrent SQLite writes → 0.23s, 0 errors after WAL.
  5. thinkingBudgetTokens: 1500 in ~/.claude/settings.json to minimize per-request thinking tokens.

After all of this: the freeze pattern is identical. The bottleneck is upstream of the client, at the API gateway level.

---

Diagnosis

The throttle:

  • Triggers around the 3rd–4th concurrent session.
  • Returns no visible error — sessions hang on "thinking" with no toast, no log entry, no Retry-After.
  • Does not scale with plan tier (Max 5x and Max 20x show identical thresholds — this is the bug).
  • Is independent of token / RPM quota (#46037 confirms users are far below their cap).
  • Is also independent of MCP / model / network configuration (verified above).

Root cause: a per-account concurrent-stream cap that is decoupled from the plan multiplier and silently buffers (rather than rejects) requests once exceeded.

---

Proposed fixes (ordered by impact)

1. Tie concurrent-stream quota to plan multiplier (highest impact)

The Nx in Max Nx should grant N× concurrent streams against the Pro baseline, not just N× cumulative tokens. Today the concurrency cap appears constant across tiers, defeating the main reason power users upgrade from 5x to 20x.

Implementation sketch: at the rate-limiter (gateway), key the concurrent_streams bucket by (account_id, plan_tier) and read the multiplier from the same source as token quotas.

2. Surface throttling to the client (low cost, high UX)

Currently throttled sessions hang silently. Return a structured 429 with Retry-After and a clear error.type. Claude Code can then show "Rate-limited: queued, retrying in Xs" instead of an indistinguishable spinner.

Implementation sketch: the gateway already knows it's throttling; emit the error rather than buffering the request silently.

3. Client-side request queue with exponential backoff

Claude Code (CLI) should treat 429s on streaming requests as queueable, not fatal. A small per-process queue with (base_delay × 2^attempt) + jitter would keep sessions alive instead of freezing them, and surface queue position in the UI.

4. Burst allowance for session startup

Most freezes happen on the very first request of a new session (typical pattern: mimir_status / mimir_context calls launched all at once). A small burst credit (e.g. allow 4× steady-state RPM for the first 30s of a new session) would absorb the spike when N sessions launch within the same second.

5. Session affinity at the LB layer

If the throttle is per-edge-node rather than per-account globally, sticky sessions (account_id → consistent backend) would smooth bursts when many sessions launch simultaneously.

---

Reproducibility

Reliably triggers the issue in under 60 seconds on Max 20x:

# In four separate project folders, each in its own terminal:
claude --model claude-opus-4-7 -p "Read README.md and summarize"

Expected: 4 summaries return.
Actual: 1 returns; 3 hang for the remainder of the 5-hour session window with no error.

---

Why this matters for paying customers

The current behavior makes Max 20x effectively equivalent to Max 5x for parallel-workflow users (the exact cohort paying $200/month). The pricing page implies 20× capacity, but the binding constraint is concurrency — which doesn't scale. Fixing fix #1 alone would resolve most of the open issues listed below.

---

Related issues: #53922, #46037, #38335, #41788, #54750, #8449 — all describe the same throttle from different angles. Consolidating into a single concurrency-quota fix would close most of them.

Happy to provide additional telemetry, network-level captures, or test on a different machine if that helps triage.

cnighswonger · 3 months ago

Adding an independent confirmation that this is account-scoped concurrency, not per-request-size:

We just hit "Server is temporarily limiting requests · Rate limited" on a cache-warmer ping — a request whose entire payload is on the order of 50 tokens (one read of a small status file, one-token output). No heavy concurrent traffic from our session at the time. We're on Max 20x with multiple Claude Code agents on a single account (which is the relevant load).

That request shape can't trip per-request quotas, weight, or model-specific limits. The only mechanism it can plausibly trip is exactly what @Manuelreyesbravo describes above: an account-scoped concurrent-stream cap saturated by other in-flight requests on the same account, regardless of their individual sizes.

Reinforces the architectural argument: the concurrency cap doesn't scale with plan tier, and the symptom shows up across the entire request size spectrum — from heavy multi-tool sessions down to ~50-token pings.

— Chris

cnighswonger · 3 months ago

Follow-up with hard data from a 15-minute capture last night.

Inserted a logging tee between our cache-fix proxy and the upstream API to catch the next 429 in full HTTP fidelity. Captured 88 consecutive 429s between 2026-05-08 00:06:33 → 00:21:30 UTC. Several findings that sharpen the picture from your analysis:

  1. "Silent hang" is actually retry-budget exhaustion. CC retries each rate-limited request 6+ times silently with exponential backoff (~0.86s → 1.4s → 2.2s → 3.8s → 6.9s → 8.4s spacing — textbook) before surfacing the error to the user. The visible "Server is temporarily limiting requests" message represents the final failure on a single logical request that already absorbed multiple silent retries. Reframes the user perception from "one API call failed" to "this entire request is exhausted."
  1. The retry signal is x-should-retry: true in the response headers, not Retry-After. CC reads the boolean and infers backoff timing on its own.
  1. Anthropic strips anthropic-ratelimit-* headers from error responses. Non-error responses include q5h_utilization, representative_claim, etc. — the 429 strips them. So even sophisticated clients can't see which limit was hit. This is a real observability gap.
  1. Confirms account-wide concurrent stream diagnosis. The burst captured here started during a wake-from-idle moment (multiple agents resuming activity within seconds of each other). All requests came from one account, agents on the same host, varying session shapes. Burst occurred at 00:06 UTC — far outside any old-peak schedule. Matches your fix #4 framing precisely.

Capture spans 88 × 429 / 1 × control / inter-arrival mean 10.3s / latency p95 882ms (gateway-level rejection, not deep-backend stalls).

— Chris

kcarriedo · 3 months ago

We hit this exact pattern when stress-testing bulk-spawn workflows. A few observations from outside Anthropic, in case they help while you wait on a CLI-side fix:

  1. The "not your usage limit" error appears to be a per-account concurrent-init throttle that's separate from the 5-hour quota. A short stagger between spawns (we've seen ~2–5s between launches) gets all sessions through reliably without changing the underlying limits.
  1. Worktree-isolated sessions help with file-tree conflicts but don't help with this — the rate-limit is upstream of git state, so per-worktree isolation alone doesn't change the spawn admission behavior.
  1. If you're scripting this, treating each claude spawn as a subprocess with its own process group + reading stdout/stderr on a bounded channel makes the failures recoverable: when one session hits the limit, you can detect the error string, back off with jitter, and respawn that single session without nuking the others. Killing the whole batch and restarting is what amplifies the problem.

For documentation, +1 on surfacing the burst-vs-sustained limit explicitly. Right now the only way to discover it is by hitting it.

Happy to share more of the harness pattern if useful — we ended up building one for a polling/scheduler service that spawns Claude as a subprocess on an interval and had to solve the same admission-control problem.

Manuelreyesbravo · 3 months ago

@kcarriedo — we ran the same investigation on our end after your comment. Several of your findings check out exactly, and we have a few additions worth folding in.

---

What we verified

1. Process group isolation is not happening today

We confirmed this from a live system with 3 concurrent sessions:

PID=28908  PGID=5738  PPID=11414  ← claude session A
PID=41215  PGID=5738  PPID=11414  ← claude session B
PID=41579  PGID=5738  PPID=11414  ← claude session C
PID=11414  PGID=5738              ← Electron parent

All three claude processes share the same PGID as the Electron parent. There is zero process group isolation between sessions today. Your setsid() suggestion is architecturally correct — each session should be its own process group leader so signals and resource pressure don't cross-contaminate.

2. The "killing the whole batch amplifies the problem" is exactly what we observed

From our main.log, the failure cascade looked like this:

21:22:13  setFocusedSession  →  focus switched to another session
21:22:24  LocalMcpServerManager: Closing [MCP server]
21:22:24  [MCP server] disconnected
21:22:24  Connecting to [MCP server]  →  reconnected in <1s

This happened 4 times in 14 minutes. Each reconnect kills any in-flight tool call on the previous connection → session surfaces "Tool result could not be submitted" → user perceives session as broken → restarts the whole batch → new concurrent-init burst → re-triggers the admission throttle. The restart is what amplifies the problem, exactly as you described.

3. The detectable error string in --output-format stream-json

The terminal error string that appears in stdout after retry-budget exhaustion is:

"Server is temporarily limiting requests · Rate limited"

This survives in the stream-json output and is detectable without parsing the full response structure — a simple in check on the output line is enough to identify the condition before deciding whether to backoff-and-respawn vs kill the batch.

---

What we'd add to your harness pattern

Based on the above, two additions worth considering:

A. Detect the pre-exhaustion signal, not just the terminal error

The retry signal x-should-retry: true appears on each of the 6 silent retries before the terminal message. If you're reading stderr through the bounded channel, the intermediate "attempt N/6" state (once #57134 is fixed and CC surfaces it) would let you backoff earlier — before the session actually dies — rather than waiting for the terminal string.

B. Stagger + process group isolation together

The 2–5s stagger you mentioned gets sessions through the admission window. Combined with setsid() isolation, a session that does hit the throttle after staggering can be respawned independently without the SIGTERM propagating to the other sessions via the shared PGID.

---

Would you be open to sharing the harness skeleton? Specifically the bounded-channel read + selective respawn logic. We're trying to document a robust reference pattern for the issue tracker that could inform a client-side fix in CC itself — your implementation would be the clearest proof-of-concept available.

— @Manuelreyesbravo

Manuelreyesbravo · 3 months ago

New data point — 500 Internal Server Error triggers the same concurrent-slot starvation as 429

Observed today (2026-05-18) with 5 concurrent sessions on Max 20x / Opus 4.7:

One session received a 500 api_error:

API Error: 500 {"type":"error","error":{"type":"api_error","message":"Internal server error"},"request_id":"req_011CbAn7hVkKGN1KjuCmYSbn"}

From main.log, the CycleHealth tracker confirmed:

[CCD CycleHealth] unhealthy cycle (107s, hadFirstResponse=true, reason=api_error)

The session had already received a first response, then hit the 500 and entered the silent retry loop. It held the concurrent stream slot for the full 107 seconds of retry budget before being marked unhealthy.

During that 107s window, all 4 remaining sessions froze simultaneously — same symptom as the 429 burst, but triggered by a backend 500 instead of the admission throttle.

Implication: the retry-budget exhaustion pattern @cnighswonger documented isn't exclusive to 429s. A 500 occupies the same slot for the same duration before releasing it. If the concurrent-stream cap is the binding constraint, a single 500 is enough to starve all other sessions — even if none of them individually hit a rate limit.

This makes Fix #2 (surface the error with a structured cause) and Fix #7 (show retry state) even more important: today there is no way to distinguish "session is thinking" from "session is burning through retries on a 500" from the user's perspective.

— @Manuelreyesbravo

Manuelreyesbravo · 3 months ago

Third failure mode under concurrent session pressure — spawn failure with 'native binary not found' when binary exists

Observed today (2026-05-19) with 5 concurrent sessions on Max 20x / Opus 4.7:

From main.log:

[error] Session query error: Claude Code native binary not found at
/home/manu/.config/Claude/claude-code/2.1.64/claude.
Please ensure Claude Code is installed via native installer
or specify a valid path with options.pathToClaudeCodeExecutable.

[CCD CycleHealth] unhealthy cycle (0s, hadFirstResponse=false, reason=no_response)

The binary exists at that exact path (236MB, correct permissions). The session failed to spawn at all — hadFirstResponse=false and duration 0s confirm the process never started. This is not a missing binary issue; it's a spawn failure under resource pressure.

Context: at the time of failure, 5 concurrent claude sessions were running, each consuming ~380MB RSS (~2GB+ total). The likely cause is EMFILE (too many open file descriptors) or memory pressure preventing execve from succeeding, with the error surfaced as a generic "binary not found" instead of the actual OS error code.

Why this matters for this thread: this is a third distinct failure mode triggered by concurrent session pressure:

  1. 429 burst → silent retry loop holds concurrent stream slot for ~23s per attempt
  2. 500 api_error → same silent retry loop holds slot for 107s (documented in previous comment)
  3. Spawn failure → session dies immediately (0s), but the failure message is misleading — "binary not found" when the binary is present makes it impossible to diagnose without checking main.log

All three produce the same user-visible symptom (frozen or dead session, no actionable error), and all three are invisible without log access. The common thread: Claude Desktop has no mechanism to surface the actual failure cause — OS error, API error code, or retry state — to the user.

— @Manuelreyesbravo

Manuelreyesbravo · 3 months ago

New evidence: "Usage limit reached" displayed when actual quota is 20-30% used

Date: 2026-05-21 ~15:16 local time
Plan: Max (5x)

What happened

Three sessions terminated simultaneously with reason=api_error within 26 seconds of each other:

15:15:40 [CCD CycleHealth] unhealthy cycle for local_19e65ce8 (461s, hadFirstResponse=true, reason=api_error)
15:15:59 [CCD CycleHealth] unhealthy cycle for local_002c78ba (1089s, hadFirstResponse=true, reason=api_error)
15:16:06 [CCD CycleHealth] unhealthy cycle for local_0a909d89 (83s, hadFirstResponse=true, reason=api_error)

All three sessions showed "Usage limit reached" in the UI at the moment of termination.

At the same moment, the plan usage dashboard showed:

  • Current session: ~20% used (resets in 2h 30m)
  • Weekly / All models: ~30% used

Why this is different from real quota exhaustion

Real quota exhaustion means all sessions would fail immediately and no new requests would be accepted. What we see instead:

  1. Sessions had already received first responses (hadFirstResponse=true) — quota was fine when they started
  2. They were mid-cycle when the api_error hit all three simultaneously — simultaneous api_error across concurrent streams is the signature of a server-side admission throttle, not a per-account quota counter
  3. The 1089s cycle (local_002c78ba) had been running ~18 minutes on a single request, which matches the known pattern of a request caught in silent retry loops (see #57134) before ultimately failing
  4. After killing all processes and restarting, sessions resumed normally — real quota exhaustion doesn't self-resolve in seconds

The misleading UI message

The string "Usage limit reached" is the same message shown when a user genuinely exhausts their subscription quota. Using it for concurrent stream cap rejection causes users to:

  • Believe their subscription is broken or depleted
  • Stop working unnecessarily (quota is actually fine)
  • Distrust the usage dashboard (dashboard says 20%, UI says "limit reached")

Suggested fix: Distinguish the error message. Options:

  • "Too many concurrent sessions active — please wait a moment and retry"
  • "Server capacity temporarily unavailable — your usage quota is not affected"
  • Or surface the actual HTTP status (429 vs quota-exhausted) so the client can show the right message

Contributing factor: LocalMcpServerManager cascade

In the 4 minutes before the crash, LocalMcpServerManager killed and reconnected the MCP server 4 times due to rapid tab switching (see #58898). Each reconnection triggers a replaceRemoteMcpServers call that injects new server state into all active sessions, generating additional in-flight API activity. This amplifies the stream count right before the cap is hit.

15:13:59 [LocalMcpServerManager] Closing mimir
15:14:00 [LocalMcpServerManager] Closing mimir   ← double close in 1s
15:14:00 [LocalMcpServerManager] Connecting to mimir
15:14:01 [LocalMcpServerManager] Connected to mimir (125 tools)
15:14:01 [LocalMcpServerManager] mimir disconnected
15:14:01 [LocalMcpServerManager] Connecting to mimir
15:14:01 [LocalMcpServerManager] Connected to mimir (125 tools)
15:15:20 [LocalMcpServerManager] Closing mimir   ← again

This is a compounding failure: #58898 inflates stream count → cap hit → #53922 terminates sessions → UI shows wrong error → user thinks quota is exhausted.

Summary

| Signal | Value |
|---|---|
| Sessions failed simultaneously | 3 |
| Time window | 26 seconds (15:40–16:06) |
| Current session quota used | ~20% |
| Weekly quota used | ~30% |
| Sessions recovered after process kill+restart | Yes (immediate) |
| hadFirstResponse on all failed sessions | true |
| UI message shown | "Usage limit reached" |
| Actual cause | Concurrent stream cap (api_error) |

The dashboard and the UI are contradicting each other. This erodes trust in the plan usage display and in the reliability of the Max subscription for concurrent workloads.

kcarriedo · 3 months ago

This is a distinct failure mode from the per-session rate limit errors — it's a burst concurrency cap that fires during the bootstrap phase when multiple sessions handshake simultaneously. The "not your usage limit" language in the error is accurate but unhelpful without knowing what the actual concurrency threshold is.

The retry-with-backoff suggestion here is good. A few things that help in practice while this is being fixed:

Stagger spawning. Rather than bulk-spawning 10 sessions at once, launching them with a 2–3s delay between each avoids hitting the burst threshold. Not elegant, but nearly eliminates the failure on most runs.

Separate the bootstrap from the work. The API handshake on startup is the expensive part. If your script waits for session N to report "ready" before spawning N+1, concurrency spikes are avoided entirely — though this adds total spawn time.

Explicit error surface. The bigger issue is that the error gives no signal on retry timing or current concurrency slot availability. Even a Retry-After header in the response would make client-side backoff well-calibrated rather than guessed.

Related: #62426 hits this from the sustained multi-session angle rather than burst-at-reset, suggesting the concurrency throttle applies at both burst and steady-state.

github-actions[bot] · 1 month ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.