Skip to content

An agent stopped by a usage limit is continued once the limit resets - #93

Merged
andrin-n-dream merged 1 commit into
masterfrom
usage-skill
Oct 2, 2026
Merged

andrin-n-dream merged 1 commit into
masterfrom
usage-skill

Conversation

@andrin-n-dream

Copy link
Copy Markdown
Contributor

When a usage limit runs out, Claude Code ends the turn and waits for a person, so an agent working on its own stayed stopped after the reset. Claude cannot undo this itself: the StopFailure hook's output is ignored.

  • Resume (server/src/session/resume.ts): reads the stop from the transcript ("error":"rate_limit", the record measured in a real stop) and parks a queued continue todo with notBefore set to the reset. The dispatcher types it the way it types any todo. The wait is the reset plus 2 min, and at least 5 min after the stop. Answered stops are kept in state.json. The todo goes ahead of todos queued before the stop. If the human types first, it is dropped.
  • Switch: continue automatically replaces the third row of the top bar's usage readout (PUT /api/auto-continue). It is passed on to every linked machine. The readout shows the two most-used limits; a tie goes to session or week.
  • Fix: claude -p /usage now runs with --no-session-persistence. Every reading had left a transcript behind (1,077 of them, 12MB).

Verified on a scratch instance with a stand-in agent that writes the measured stop: four agents parked, one taken over by a keystroke and dropped, three resumed at 14:11:16 against a notBefore of 14:11:14.9. The switch is hit by elementFromPoint at every rung, and it reached a linked peer.

Not verified: what a real Claude's screen shows at a limit stop. If it shows a dialog, the continue waits rather than being typed into it.

🤖 Generated with Claude Code

When a limit runs out, Claude Code ends the turn and waits for a person, so
an agent working on its own stopped and stayed stopped after the reset.
Nothing inside Claude can undo that: the StopFailure hook fires with
matcher rate_limit, but its output and exit code are ignored.

The server now does it. session/resume.ts looks at every running Claude's
transcript every 15s. When the newest record is a limit stop, it parks a
todo, "continue", queued with notBefore set to the reset. The dispatcher
types it the way it types any todo: into a Claude at rest with an empty
input box, claimed on disk first. The server types nothing outside that
path.

- The stop is read from the transcript. Measured in a live one: an
  assistant record with "error":"rate_limit" and "isApiErrorMessage":true,
  text "You've hit your session limit · resets 4:10pm (UTC)", then
  turn_duration. This needs no hook and writes nothing into anyone's
  settings, the same rule attention follows.
- The reset comes from that sentence, or from /usage when the sentence
  cannot be read. The wait adds 2 minutes for rounding and is never less
  than 5 minutes from the stop. Without that floor, an early continue that
  stops again would be retried every 15s.
- Answered stops are kept in state.json (limitStops). Otherwise a deleted
  todo, or a restart, would park the stop again.
- The continue goes ahead of todos queued before the stop and holds them
  back. If the human types first, it is dropped.
- A top-bar switch, "continue automatically", turns it off. The setting is
  passed on to every linked machine, since the limit is the account's.
  The usage readout keeps two rows of limits, the two most used; on a tie
  the earlier one in /usage's order wins, so fable never wins a 0% tie.

Also: `claude -p /usage` now runs with --no-session-persistence. Every
reading had left a transcript behind: 1,077 of them (12MB) in
~/.claude/projects/-home-andrin--config-switchboard. With the flag, a
reading writes none (checked by listing the directory after a reading).

Measured on a scratch instance, with a stand-in agent that writes the
measured stop and logs its input. Four stopped agents each got a parked
continue. One key typed into one of them dropped its todo. The other three
got the paste and a separate Return at 14:11:16, against a notBefore of
14:11:14.9. In the browser the switch is hit by elementFromPoint at every
rung, and switching it off at a gateway set a linked peer to false.
Each new guard was broken once and its test watched to fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@andrin-n-dream
andrin-n-dream merged commit 2488236 into master Oct 2, 2026
1 check passed
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