Skip to content

Establish whether a backgrounded session can sit in a cap retry loop, and stop claiming a fixture is observed - #177

Merged
MJohnson459 merged 1 commit into
mainfrom
cap-retry-reading-453
Aug 14, 2026
Merged

MJohnson459 merged 1 commit into
mainfrom
cap-retry-reading-453

Conversation

@MJohnson459

Copy link
Copy Markdown
Contributor

Evidence first: the fixture was binary-derived, and it says nothing about caps.

cap.rs carried Session limit reached · Retrying in 5m (9:50pm) · attempt 2/10 under a comment calling it "the wording every --bg dispatch of the
built-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/M whenever a retried request carries usage-limit headers. So the
template 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 one
path 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 its
remote/BYOC runner environment, never for a local --bg launch. Without it a
retry 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/10 on
2026-08-13, listed blocked with a live supervisor — indistinguishable from a
cap by state.

So CapReading gains 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 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 warnings clean, cargo fmt --all.
Branch cap-retry-reading-453, one commit, not pushed.

…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`.
@MJohnson459
MJohnson459 merged commit 8ebc01d into main Aug 14, 2026
7 checks passed
@MJohnson459
MJohnson459 deleted the cap-retry-reading-453 branch August 14, 2026 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant