Establish whether a backgrounded session can sit in a cap retry loop, and stop claiming a fixture is observed - #177
Merged
Conversation
…xture observed `cap.rs` carried `Session limit reached · Retrying in 5m (9:50pm) · attempt 2/10` under a doc comment calling it "the wording every `--bg` dispatch of the built-in claude hits first". It is not observed — it was grepped out of the agent's binary, and reading it as an observation sent a reviewer looking for a cap that retries. Checked against the agent itself rather than left as a claim. The string is real: a spinner component renders `<type> limit reached · Retrying in <delay> (<reset>) · attempt N/M` whenever a retried request carries usage-limit headers. A usage cap never reaches it, because a 429 carrying those headers is classified non-retryable under a subscription login; the turn ends and the session sits at the checkpoint message we have actually seen, `You've hit your session limit · resets 6:40pm`. The one path that waits a cap out and resumes by itself — waiting until the reset, up to six hours — is gated on CLAUDE_CODE_RETRY_WATCHDOG, which the agent's remote runner sets and a local `--bg` launch does not. So DESIGN.md §8 stands, and now says why. The risk runs the other way, which is what earns code. A rate limit carrying none of those headers *is* retried, and its label is the API's own message, which says "rate limit" in it — a signature this module matches. That badges a mid-turn session as capped, and since #428 the sweep stops its target before resuming it, so a nudge there does not add a redundant turn but ends the one already running. Backgrounded sessions do reach that banner: a `--bg` session on 2026-08-13 sat at `529 Overloaded · Retrying in 3s · attempt 3/10`, listed `blocked` with a live supervisor, indistinguishable by state from a cap. So `CapReading` carries `retrying`, read from the retry wording in the same window as the reset time. Such a session badges `⚠ capped · retrying ↻21:50` rather than reading as healthy, never reports its reset as passed, and the sweep skips it and counts it in what it reports. The fixtures in the TUI tests that stood in for a capped session are now the observed cap message, with the binary-derived string kept only where it belongs: as the shape the retry reading has to recognise, labelled as binary-derived. Verified with `cargo test --workspace` (880 tests), `cargo clippy --workspace --all-targets -- -D warnings`, and `cargo fmt --all`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Evidence first: the fixture was binary-derived, and it says nothing about caps.
cap.rscarriedSession limit reached · Retrying in 5m (9:50pm) · attempt 2/10under a comment calling it "the wording every--bgdispatch of thebuilt-in claude hits first". Traced to its source in the agent's binary: a
spinner component renders
<type> limit reached · Retrying in <delay> (<reset>) · attempt N/Mwhenever a retried request carries usage-limit headers. So thetemplate is real; the observation was not.
A usage cap cannot reach it. The agent's retryability rule ends, for a 429,
return !isSubscription() || isEnterprise() || carries_no_unified_ratelimit_headers,and a genuine cap always carries
anthropic-ratelimit-unified-representative-claim— the very header that supplies the "Session limit" wording. So a cap is
non-retryable: the turn ends and the session sits at the checkpoint message we
have actually observed,
You've hit your session limit · resets 6:40pm. The onepath that waits a cap out and resumes by itself — waiting until
anthropic-ratelimit-unified-reset, capped at six hours, with maxRetries 300 —is gated on
CLAUDE_CODE_RETRY_WATCHDOG, which the binary sets only in itsremote/BYOC runner environment, never for a local
--bglaunch. Without it aretry wait is hard-capped at 60s. DESIGN.md §8 stands, and now records why.
The hazard runs the other way, and that is what earns code. A rate limit
carrying none of those headers is retried, and the agent labels that retry
with the API's own message, which contains "rate limit" — a signature this
module matches. That badges a mid-turn session as capped, and since #428 the
sweep stops its target before resuming it, so a nudge there does not add a
redundant turn but ends the one already running. Backgrounded sessions do reach
that banner: one sat at
529 Overloaded · Retrying in 3s · attempt 3/10on2026-08-13, listed
blockedwith a live supervisor — indistinguishable from acap by state.
So
CapReadinggainsretrying, read from the retry wording in the same windowas the reset time. Such a session badges
⚠ capped · retrying ↻21:50ratherthan reading as healthy, never reports its reset as passed, and the sweep skips
it and counts it in what it reports rather than passing over it in silence.
DESIGN.md §8 gains the fourth badge shape, the carve-out, and the provenance of
the no-retry claim.
The misleading comment is gone. The binary-derived string stays only where it
belongs — as the shape the retry reading must recognise — labelled as
binary-derived; the TUI fixtures that stood in for a capped session now carry
the observed cap message instead (the logs stub is double-quoted so it can hold
the apostrophe the real message has).
Follow-up filed as task #457: the same read turned up an auto-continue-after-
reset path in the agent, gated on a server-side flag rather than an environment
one, which would break the same assumptions if it ever turned on.
Verified:
cargo test --workspace(880 tests, all pass),cargo clippy --workspace --all-targets -- -D warningsclean,cargo fmt --all.Branch
cap-retry-reading-453, one commit, not pushed.