[BUG] Contradictory 'session limit resets at X' times across one session; background subagents die terminally and reset projections silently roll forward

Status Open
Maintainer reply None cached
Activity 9 comments · opened Jul 3, 2026

Environment

Claude Code CLI on macOS (darwin 24.6.0), subscription auth (not Bedrock), model claude-fable-5, heavy use of background subagents via the Workflow/Agent tools.

What happened (timeline, one session, 2026-07-03, America/Los_Angeles)

  1. ~06:08 PT: a background workflow subagent failed terminally with: "You've hit your session limit - resets 8am (America/Los_Angeles)".
  2. 06:08-11:38 PT: the MAIN session loop kept working fine the whole time (deploys, tool calls), and two more multi-agent workflows ran to completion (~3.3M subagent tokens). Only individual subagents were dying.
  3. 11:38 PT: another background subagent failed terminally with: "You've hit your session limit - resets 2:10pm (America/Los_Angeles)".
  4. After 2:10pm PT the user was STILL blocked, now with errors projecting a reset around 7pm PT (2:10pm + 5h -- exactly one rolling-window length later, consistent with freshly-resumed consumption re-pinning the projection).
  5. The user purchased usage credits, which unblocked everything immediately.

Why this is a bug (or at least three UX defects)

  1. The reset time reads as a promise but is a snapshot. Each error message states an absolute clock time, but the 5-hour rolling window means any consumption after the snapshot silently moves the real reset later. A user who waits until the stated time and finds a LATER time in the next error reasonably concludes state corruption (this user's exact question: "Is there an auth token corruption?"). The message should either say the window is rolling ("earliest possible resume, moves with continued usage") or the client should show live window state.
  2. Background subagents share the pool invisibly and die terminally. The main loop and its background workflow agents draw from one subscription window, but there is no attribution in the error: the user cannot see that their own session's background fan-out is what consumed the window and keeps pushing the reset forward. Subagents that hit the limit die with a terminal failure rather than pausing/queueing until the window frees; after the user purchases credits, dead subagents do not resume -- the orchestrating session has to detect the death and respawn manually.
  3. Inconsistent enforcement within one session. For hours, the main loop succeeded while spawned subagents were rejected with session-limit errors. If the limit applies per pool, the main loop should have been throttled too; if subagents have a separate/earlier cutoff, the error message should say so. The mixed behavior amplifies the corruption impression.

Expected

  • Rate-limit errors state that the window is rolling and the projection is a lower bound, or the CLI surfaces live usage-window state (e.g. in /usage).
  • Subagent limit-failures are queued/retried when the window frees or credits are added, rather than requiring manual respawn.
  • Reset projections that account for the session's own in-flight background consumption.

Related

#56570 (closed) reported the reset-not-triggering symptom; this report adds the rolling-window snapshot semantics + background-subagent attribution + terminal-subagent-death angle, reproduced across multiple errors in a single session.

View original on GitHub ↗

9 Comments

jrobgood · 1 month ago

Material update from the affected user that changes the likely diagnosis: the client's /status view showed the subscription quota NOT exhausted at the same time the background subagents were failing with 'You've hit your session limit - resets at X'.

Combined with the other observations, the evidence now points away from rolling-window semantics and toward a POOL MISMATCH with misattributed error text:

  • The main session loop was never throttled -- it ran continuously for hours while spawned subagents were rejected. A genuinely exhausted shared window should have rejected both.
  • /status showed subscription headroom throughout.
  • Purchasing extra usage credits (raising the monthly extra-usage limit) instantly unblocked subagent spawning -- suggesting spawned-agent traffic was being limited against the extra-usage/credits bucket (at zero), not the subscription window the error message pointed at.
  • Under this reading, the shifting reset times (8am -> 2:10pm -> ~7pm) were projections for a limiter the user was never actually up against, which is why waiting for them did nothing and buying credits fixed it immediately.

Net effect on the user: a misleading error cost real money -- they purchased $100 in extra-usage credits to route around a 'session limit' that the client's own /status said they had not hit.

Requests, updated:

  1. Error text should name the LIMITER that rejected the request (subscription window vs extra-usage credits vs org limit), not a generic 'session limit'.
  2. If spawned/background agents are billed or limited against a different pool than the interactive loop, surface that in /status and in the spawn-failure message.
  3. The original asks stand: rolling-vs-snapshot clarity, and subagents resuming rather than dying terminally when capacity returns.
jrobgood · 1 month ago

One more cost dimension from the same incident, per the affected user: DOUBLE BILLING FOR ORPHANED WORK. When the mislabeled limiter kills a background subagent terminally, the tokens that agent consumed before dying are still charged -- but the work is orphaned (our first implementation agent died at spawn after its full prompt+context was consumed; a synthesis agent died AFTER its 12 upstream finder agents had burned ~2.4M tokens, requiring manual re-synthesis). The user then purchased extra-usage credits to unblock -- paying a second time to redo/resume work the first charge already covered. When a limiter rejects an agent, either the partial consumption should not bill, or the agent should suspend-and-resume when capacity returns, so a limiter mislabel does not convert directly into double spend.

jrobgood · 1 month ago

ESCALATION -- the failure reproduced ON PAID CREDITS, and there is now concrete evidence pointing at the mechanism.

New incident (2026-07-03, ~14:35 PT): after the user purchased $100 of extra-usage credits, the next background subagent ran for 16.4 minutes (61 tool calls) and was then killed with '[implement:n1] failed: You've hit your session limit - resets 7pm (America/Los_Angeles)'. Workflow usage block, verbatim: agents_done 0, agents_error 1, subagent_tokens 374904, duration_ms 982109. Those 374,904 tokens were billed against the just-purchased credits and produced zero recoverable output (read-only recon; no disk artifacts). The user then purchased another $50 -- total $150 spent routing around this -- while the client's /status continued to show the subscription under quota.

Mechanism evidence -- the harness's auxiliary model dependency:

  1. At 14:22 PT, in the MAIN loop of the same session, a Bash tool call was blocked with, verbatim: 'claude-opus-4-8[1m] is temporarily unavailable, so auto mode cannot determine the safety of Bash right now.' So the harness routes auxiliary calls (the permission safety classifier) to claude-opus-4-8[1m] -- a DIFFERENT model from the session model (claude-fable-5).
  2. An earlier workflow in the same session emitted this diagnostic on a completed subagent: 'Note: claude-opus-4-8[1m] (the safety classifier) was unavailable when reviewing this subagent's work.'
  3. Throughout all three subagent deaths (06:08 PT 'resets 8am', 11:38 PT 'resets 2:10pm', 14:35 PT 'resets 7pm'), the main session loop was continuously healthy: 20 git commits between 03:23 and 11:38 PT and uninterrupted successful tool calls after 14:00 PT.

Coherent hypothesis: the subagent pipeline has a hard dependency on claude-opus-4-8[1m] (at minimum the safety classifier, possibly subagent supervision), and it is THAT model's limiter/availability that kills subagents -- while the 'session limit' error text and the /status display both refer to the session model's window, which was never exhausted. Users are shown a limit they have not hit, wait for resets that change nothing, and buy credits that do not map to the failing dependency -- and the killed agents' consumption bills anyway.

Requested remediation, updated:

  1. Account-side usage audit for this account covering 2026-07-03 (PT): attribute each rejection to the actual limiter/model, and refund (a) the 374,904 tokens billed to a credit-funded agent that the limiter destroyed and (b) prior killed-agent consumption. If the limiter was misattributed, the $150 in credits was spent routing around a display/error bug and @anthropics should be grateful that @jrobgood is a reasonable person and does not expect a refund, but just wants this fixed for everyone. @bcherny I'm lookin' at you buddy.
  2. Error text must name the failing model/limiter (e.g. 'auxiliary model claude-opus-4-8 capacity'), not a generic 'session limit' pointing at a window /status shows as healthy.
  3. Subagent runs should checkpoint or suspend-resume; a 16-minute read-only run destroyed at the last step is pure loss.
yurukusa · 1 month ago

Adding a user-side angle that may stop the bleeding for anyone else who lands here, plus a way to confirm the pool mismatch before spending more — because the costly part of this is buying credits that don't map to the limiter that's actually rejecting you.
The tell you already surfaced is the load-bearing one. In the same session, verbatim:

  • claude-opus-4-8[1m] is temporarily unavailable, so auto mode cannot determine the safety of Bash right now
  • Note: claude-opus-4-8[1m] (the safety classifier) was unavailable when reviewing this subagent's work

That points at an auxiliary-model dependency in the subagent path that is separate from your session model (claude-fable-5) and separate from the window /status renders. If that auxiliary model's capacity is what's rejecting spawns, then neither waiting for the session-window reset nor buying extra-usage credits maps to it — which is exactly what you saw (waits changed nothing; even a credit-funded agent died at 374,904 tokens).
A confirmation check you can run before buying anything. Scan the current session for the co-occurrence of (a) the auxiliary-model-unavailable string and (b) a subagent killed with hit your session limit, while /status shows headroom. If all three hold, the rejection is very likely auxiliary-model capacity — not your subscription window and not credits — so don't buy credits yet. Small stdin filter, tested against your verbatim strings:

aux=0; killed=0; orphan=0
while IFS= read -r l; do
  echo "$l" | grep -qE 'is temporarily unavailable, so auto mode cannot determine the safety|\(the safety classifier\) was unavailable' && aux=1
  echo "$l" | grep -qE "hit your session limit" && echo "$l" | grep -qE '\[[^]]+\] (failed|error)' && killed=1
  t=$(echo "$l" | grep -oE 'subagent_tokens[^0-9]*[0-9]+' | grep -oE '[0-9]+$'); e=$(echo "$l" | grep -oE 'agents_error[^0-9]*[0-9]+' | grep -oE '[0-9]+$')
  [ -n "$t" ] && [ -n "$e" ] && [ "$e" -ge 1 ] && orphan=$((orphan+t))
done
if [ "$aux" = 1 ] && [ "$killed" = 1 ]; then v="aux_model_capacity_likely__DO_NOT_buy_credits_before_confirming"
elif [ "$killed" = 1 ]; then v="killed_but_no_aux_signal__check_/status_first"; else v="inconclusive"; fi
echo "{\"aux\":$aux,\"killed\":$killed,\"orphaned_billed_tokens\":$orphan,\"verdict\":\"$v\"}"

On your incident's four lines it returns:

{"aux":1,"killed":1,"orphaned_billed_tokens":374904,"verdict":"aux_model_capacity_likely__DO_NOT_buy_credits_before_confirming"}

On a plain hit your session limit death with no auxiliary-model signal it returns killed_but_no_aux_signal__check_/status_first instead, so it distinguishes the two cases rather than crying wolf on every limit message.
Honest limits: this is visibility only — not prevention, not a refund. It can't stop the auxiliary-model dependency from killing subagents, it can't un-bill the orphaned tokens, and it leans on those exact strings staying stable across versions. Every real fix is on Anthropic's side. I'd narrow the asks to the two that are cheap for them and actually end the double-spend:

  1. Name the limiter — in the error text and in /status. "You've hit your session limit" has to say which pool rejected the request (subscription window / extra-usage credits / auxiliary-model capacity / org). A generic message pointing at a window /status shows as healthy is what turned a display bug into $150 of spend.
  2. Don't bill destroyed work, or checkpoint it. A 16-minute read-only subagent killed at the last step with 374,904 tokens billed and zero recoverable output is pure loss. Suspend-resume, or don't-bill-on-terminal-limiter-kill, closes the double-spend even before the mislabel is fixed.

Your rolling-vs-snapshot ask still stands, but #1 alone would have stopped the spend here, because you'd have seen the rejection was never the subscription window.

rpelevin · 1 month ago

Given the latest auxiliary-model signal, I would make this a resource-dependency envelope rather than a quota-message bug.

The invariant I would test is not just better error text. Every subagent run should carry:

  1. parent run id and subagent run id
  2. requested work envelope: session model, auxiliary reviewer/safety model, and subscription/credit/org pools consulted
  3. rejection source: exact model, pool, availability limiter, or credit bucket that denied continuation
  4. chargeability state: no-charge, partial-charge-with-checkpoint, or billable-completed
  5. recovery state: resumable checkpoint, queued retry, or terminal no-output/no-effect

The failure case to guard is a subagent burning context and tool work, then an auxiliary reviewer limiter killing it while the UI reports the session pool and /status shows the wrong state. That creates billed work with no recoverable output and no reliable way for the parent to know what actually stopped the run.

A conformance test could force the auxiliary reviewer/model unavailable while the parent session model still has quota. The expected result should bind the stop to the auxiliary limiter id, expose the same id in status and usage, and either checkpoint the subagent or mark consumed work terminal-no-effect/non-billable. If partial billing is allowed, the usage event should carry the same subagent_run_id and limiter_id as the terminal outcome.

That would separate user-side diagnosis from platform accountability: one envelope explains who stopped the run, what was charged, what is recoverable, and why the parent session continued.

kcarriedo · 1 month ago

The "reset time is a promise but actually a snapshot" problem gets worse at scale. When you have multiple background subagents running concurrently, the rolling window re-pins differently per subagent depending on when it actually consumed tokens, so each one can show a different reset projection - none of which are accurate for any of the others.

The underlying issue is that session limit accounting wasn't designed with concurrent subagent consumption in mind. Each subagent is effectively its own consumer drawing from the same pool, but the UI (and the error messages) treats it as a single-stream problem.

A few things that have helped in practice:

  • Track approximate token usage per workflow run before you launch heavy multi-agent workflows, so you can predict whether you'll hit the wall mid-way
  • Structure long-running workflows so each major phase has a checkpoint - if a subagent limit-dies, the phase state is written and the next run can resume rather than restart from zero
  • For critical workflows, keep a credit buffer available exactly for the situation you hit - credits clearing the block is the expected behavior, not a workaround

The behavior you're describing in point 4 (block persisting even after a supposed reset) is the most severe case - that reads like the rolling window refilled immediately from still-active consumption, which should at minimum surface a clear "you are still in an active consumption window" error rather than a new reset projection that also turns out to be wrong.

Worth adding: what was the approximate subagent token volume running concurrently when you hit the first limit error? Would help scope how much of this is "heavy concurrent load" vs. a pure accounting bug.

jrobgood · 1 month ago

Exact volumes, from the workflow usage blocks:

Death 1 (06:08 PT, 'resets 8am'): a 13-agent workflow -- 12 concurrent verify agents had consumed 2,407,296 subagent tokens in the preceding ~7 minutes (414s wall clock); the 13th agent (the synthesis stage) was the one killed. So yes: ~2.4M tokens of concurrent burn immediately before the rejection.

Death 2 (11:38 PT, 'resets 2:10pm'): two workflows totaling 15 agents and 3,287,294 tokens had completed in the preceding ~35 minutes; the killed agent died at spawn, ~3 minutes in.

Death 3 (14:35 PT, 'resets 7pm') is the one the load theory can't cover: a single agent, zero concurrent fan-out, 374,904 tokens over 16.4 minutes, running on freshly purchased extra-usage credits, with /status showing subscription headroom -- killed mid-run with the same session-limit text. No pool it could plausibly have been drawing on was exhausted by concurrent load at that point.

On 'credits clearing the block is the expected behavior, not a workaround' -- agreed, that's the expectation. It's also what makes this a bug report rather than a usage complaint: the credit-funded agent died anyway. Credits did not clear the block; the second $50 purchase happened after death 3, not before it.

So I'd split the incident the way you implicitly did: deaths 1-2 are plausibly a real shared-window exhaustion made illegible by per-snapshot reset projections (your 'each subagent re-pins differently' point matches our 8am/2:10pm/7pm spread exactly). Death 3 -- solitary, modest-volume, credit-funded, /status-green -- needs a different explanation, and the only anomalous signal in the same window is the auxiliary-model unavailability ('claude-opus-4-8[1m] is temporarily unavailable...') hitting the main loop's safety classifier at 14:22 PT. That's why the auxiliary-dependency theory is still standing for the third rejection, and why @rpelevin's per-run envelope (rejection source bound to a limiter id, chargeability state, recovery state) is the right shape for the fix: it would have distinguished these three deaths from each other, which is precisely what the current error text cannot do.

Your checkpoint suggestion is adopted regardless -- phase-level state now gets written to disk before any multi-stage run, which is how deaths 1 and 3 cost redo-work instead of lost work.

---

Now before anyone chimes in here, assuming any of you are humans (ahem, I see right through the cross-cutting, load-bearing, honest honesty nonsense) I will be the first to admit I should count my lucky stars that I can dogpile and burn tokens like this for $200 a month. Having said that, @anthropics is learning, pressure testing, and productizing like a beast irrespective of whether I'm sharing training data explicitly or they're just shaping resources against my workloads.

pzarzycki · 1 month ago

For this kind of invisible subagent and session-limit behavior, here's a small tool. It reads Claude/Codex session data locally so you can inspect token usage and session details without API keys or shipping logs elsewhere.

Open source:
https://github.com/pzarzycki/token-companion

If you like it - leave a star; submit an issue or request, or even a PR!

COOLak · 1 month ago

Adding related Billing Platform / cost-control context because this thread already shows the expensive failure mode: a limiter/error path is ambiguous, purchased credits enter the flow, background work can die terminally, and consumed work may not be recoverable.

This is closely related to the broader paid-work-loss and wrong-pool accounting cluster:

Sanitized public evidence hub for the broader manual-credit, extra-usage, auto-topup, entitlement-routing, cost-control, and support-routing pattern: https://coolak.github.io/anthropic-claude-billing-incident/

Request: please route this class of reports to a human Billing Platform / cost-control owner. The product needs a shared runtime+billing event envelope that names the exact rejecting pool/model/gate, ties consumed usage to a recoverable artifact or checkpoint, and distinguishes billable-completed work from limiter-destroyed work that should be resumable, credited, or refunded.