3 AIs Hit the Limit
Codex mid-task, Claude mid-loop, Grok on every review, all inside one day. Two limits came with a percentage and a reset time, one with a status code. The silence around all three was the bug.
All three of my coding assistants ran out of allowance inside one day, at different moments, in the middle of work.
Two of the three programs already report how close they are to the wall. Nothing turned that number into a handover.
Now a small hook watches the percentage and tells the session to wrap up cleanly and leave a note for the next assistant.
The next assistant is opened with three words: βtakeover from claudeβ (or codex, or grok). It reads the note and continues, unless its own meter is full too.
When all three meters are full at once, nothing starts and the note waits. Until a meter resets, the time goes to silence or to work that needs no assistant; nothing is bought on top of the plans, and my own head stays in practice.
Grok Build exposes no percentage to hooks, the thresholds are a first guess, and I have no data yet on whether this saves time.
One day, three walls
Codex reached its weekly allowance this morning in the middle of a long document task. There was no handover, so I opened Claude Code and rebuilt the state by hand from the chat: where things stood, which files were touched, what was verified and what was not.
A few hours earlier, before dawn, a headless loop running Claude Code hit a session limit. The rejection read βYouβve hit your session limit Β· resets 6:10amβ. The loopβs own detector knew βhit your limitβ and βhit your usage limitβ and nothing else, so sixteen iterations ran to nothing before I looked at the log.
Grok Build answered every review request later the same day with HTTP 402: βusage balance exhaustedβ.
Three programs, three wordings for the same event. In two of the three programs the number was there and went nowhere; in the third there was no number to have. Claude Code on Fable 5.1 at max, Codex on GPT-6 Astra at ultra and Grok Build on Grok 4.6 at xhigh share one desk and one policy, described in I Love Trios. This post is about what happens when the meters fill up.
A limit is not an outage
An outage is unscheduled and nobody can tell you when it ends. A usage limit is the opposite. It has a reset time, the program often knows the percentage before the wall, and on this desk two other assistants may have capacity at that exact moment. All of that was true for Claude Code and Codex. Grok Build, which reports nothing before a refusal, is the exception and gets its own row below.
So the failure was never the limit. It was that nothing turned a known number, or a refusal, into a handover. The percentage sat in a statusline. It sat in a transcript field. And the work still stopped as if the power had gone out.
A limit belongs to the program and its account, not to the model. Fable 5.1 does not get worse at 95 percent; at 100 percent, Claude Code stops accepting turns until the window resets. So the fix is not more allowance and not retry-until-it-works. It is a session that sees the wall coming, finishes cleanly, writes down where it stopped, and names its successor.
What each program will tell you
That is the whole basis of the design: read what is native, store it, act on it. No model call is involved anywhere in the signal path.
One snapshot per provider
The toolkit I already run as native hooks in all three programs is a small Python tool; it also generates each programβs global instructions and skills from one shared policy. It now keeps one private JSON file per provider: the windows it saw, when each resets, whether a refusal is still live, and a level: ok, warning, critical or rejected. Owner-only permissions. Nothing leaves the machine.
Three rules keep the snapshot honest. A window drops out when its reset time passes, so yesterdayβs number cannot scare todayβs session. A refusal clears when the exhausted window resets; when no reset is known, which is Grokβs case, the refusal is held for six hours and then shown as stale, not as ok. And a later, healthier-looking observation never wipes a live refusal, because a statusline at 30 percent does not mean the account stopped refusing.
Thresholds: short windows (Claude Codeβs five-hour window and Codexβs primary, also five hours) warn at 85 percent and turn critical at 95. Weekly windows warn at 97 and turn critical at 99.5, because a week at 86 percent with three days left is information, not a reason to wrap up. All four are configurable, and all four are guesses until I have seen how often they fire.
LIMIT WATCH
When a level is reached, the hook injects one LIMIT WATCH block per session and level, through whichever native event each program supports: Claude Code on UserPromptSubmit, SessionStart and PostToolUse; Codex on SessionStart and PostToolUse; Grok Build on its next PreToolUse. Once per session and level means a session hears βwarningβ, βcriticalβ and βrejectedβ once each, not on every tool call, and hears it again after a compaction or a resume, when the earlier text is gone from its context. The Claude Code statusline shows it too, as β!! LIMIT WARNING 90%β.
The harness is advisory by design. It never blocks a prompt or a tool call. It never asks the model to continue after a stop. It never retries a provider. And it stays silent inside subagents, which have no business wrapping up the parentβs work. Those four rules are the part I care about most: a limit watcher that blocks calls or auto-retries is a second source of surprises, and the point of this was to remove one.
Wrap up, do not push through
A warning is useless without a fixed answer to βso what nowβ. That answer is one contract file, the same for all three programs, generated into a native βtakeoverβ skill in each. The order:
- Stop starting new work. Warning: reach a safe stopping point after the current step. Critical or rejected: finish only the step in flight, abort what cannot finish safely, and say so.
- Leave every repository consistent. Commit and push under the normal rules: no skipped hooks, no force push, no STOP marker removed to make a tree look clean.
- Update the ledgers the work already keeps, so the lasting state lives in the repository, not in the chat.
- Write the takeover record (the command below).
- Tell me in one line: which limit, when it resets, and βopen another agent and say: takeover from claudeβ. Then end the turn. Nobody waits for the window inside the same session.
ai-workspace takeover write --provider claude --root "$PWD" --reason "five_hour 96%" --body-file - <<'END'
## Task
## Done (verified)
## Next, in order
## Loops and automation
## Open risks
ENDThe toolkit records what it can check on its own: branch and head, dirty and unpushed counts, and the providerβs limit snapshot. It also records the resume pointers under the project: loop directories with their STOP markers, the saved runtime lane, and the last limit or failover lines of their logs. The session writes the body: the task, what is done with evidence, next steps in order, which loops exist and how to relaunch them, open risks.
That split is the point. The machine records the facts it can check; the model records the judgment. When I rebuilt the Codex task from the chat, both halves were missing.
Takeover from claude
The other side of the contract is just as fixed. A session told βtakeover from claudeβ, in any of the three programs, starts with two commands:
ai-workspace takeover status
ai-workspace takeover show --root "$PWD"The first says which providers are limited as of the last observation, including the one you are running in. The second prints the latest record for the project plus a fresh scan of its loop pointers, because loop state can move after a record is written. Then the session reads the record and every ledger it names, in full; pulls; checks that head, dirty count and unpushed count match the record; and inspects any difference before touching anything.
The record is a pointer and a to-do list. It is not authority. It cannot grant a permission the previous session did not have, and βthe last session said soβ is not a reason to do anything the repositoryβs rules forbid. The successor re-derives the work from those rules.
Loops get extra rules. A loop I stopped stays stopped. A loop paused by a limit is relaunched only on the successorβs own provider lane, with the loopβs documented command, and only when the loopβs policy allows it. When the work is picked up, the record is marked consumed with ai-workspace takeover done. If the successorβs own provider is also limited, it does not start. It says which providers remain usable and stops there. Three limited assistants is a real state, and the right answer to it is a sentence, not an attempt.
When all three are full
All three meters can be full at once, usually late in the week, when the weekly allowances run out. That is expected. The successor does not start, the record waits, and the time until the first meter resets (hours for a session window, days for a weekly one) goes to one of two things: silence, or the kind of work that needs no assistant at all. Reading. Thinking a design through on paper. Writing the spec the assistants will get when the meters reset.
I accept that for two reasons. The first is money. The meters fill because every task runs the three models at their highest reasoning settings; that is the output I pay for. I could buy extra usage and keep going; I do not. A full meter means the plan was used to the end and nothing was bought on top of it; waiting for a reset costs nothing. The second is my own head. When there is always a model to lean on, I get out of practice at the parts that are mine to do: holding a whole problem at once, deciding what not to build, noticing what is missing. Days without the assistants are when those parts get done.
The loops and the console
The loop that burned sixteen iterations is not the ticket-shipping loop. Its detector learned the new phrase the same morning; it now switches lane on a classified limit, reports it into the same snapshot and writes a takeover record when every provider is exhausted. The ticket-shipping loop in my cockpit was already further along: it fails over across provider lanes after an hour blocked and fails back on recovery. What it did not cover was the interactive console session that watches it, which is an ordinary session and can hit a limit like any other. That is what the new harness covers there: the console hands over instead of dying with the batch state in its head.
The one edit I did not make is inside that loopβs template, where the lane-down path would report into the same snapshot. A batch is live, and templates are never edited under a live batch, so that edit waits.
Parity and the second machine
Each program has its own hook events and its own native skill, so drift is the default state. The toolkitβs check verifies the hook registrations in all three programs, the generated skills and the contract together, in one run. Eighteen tests cover the new module, seven of them added after an independent review of the first version found real gaps: a refusal that could age out instantly, a status line that could wipe it, a resumed transcript that could resurrect it. The suite is at 306, all passing.
A second machine gets the same files through an allowlisted packet exported from a reviewed dotfiles revision. The rule I wrote down today for that path: replicate, never reinvent. A harness rebuilt from memory on a second machine is a different harness, and a different harness has different gaps.
What is not known
Some of this is scaffolding, and I would rather list the holes myself.
- Grok Build exposes no allowance percentage to hooks or the statusline, so a Grok session gets no warning before a refusal and will get none until the program exposes one. After a refusal, the record mostly serves the other two programs and the next takeover check.
- The thresholds are a first guess. I have no data on how often the warning fires, how often it fires too early, or how much time a takeover saves against the manual reconstruction I did from the Codex chat. I am not claiming savings; I am claiming a procedure that exists and is tested.
- A session that ignores the advisory simply runs into the native limit, the same as before. The harness never blocks, so it cannot force a wrap-up. In that case there is no record, and the state has to be rebuilt from the ledgers and the chat, as it was this morning.
- Records are machine-local. Nothing is synced between machines. A limit hit on one machine is invisible on the other.
- The Codex transcript field and Grokβs silence are dated observations from 10 September 2026. Either may change with the next release, and the parity check was not built to notice a renamed transcript field; a silent format change would most likely show up as a warning that never fires.
The three screens in the reel will happen again. The difference next time is supposed to be small and boring: the session at 95 percent says so, writes down where it stands, and the next one starts from a record instead of a chat.
I Love Trios β the three programs, one desk, one policy.
Grok Joins the Party β how Grok Build got its seat.
Graph Engineering: Evidence In, Guesswork Out β the same toolkit deciding what may run next.
Astra Γ Fable: Full Power, Less Waste β maximum settings, audited.