AI Workflow · part 9
[Dev Workflow] When Your Quota Runs Out Mid-Task: A Live-State Handoff Protocol for Claude Code and Codex
❯ cat --toc
- The thing that dies isn't your knowledge base — it's the live state
- Save on state transitions, not on a timer
- The handoff file: seven sections, and a rule never to trust the last step
- Three gotchas that cost real time
- Dogfooding it: Codex built its own staleness hooks
- The trap: my hook flagged a 19-hour training run as "up for grabs"
- The fix: a phase-aware state machine (and why the rule, not the hook, was the bug)
- The last mile of a lesson: logged ≠ documented ≠ templated ≠ enforced
- Conclusion: the checklist
TL;DR
When an AI coding CLI runs out of quota mid-task, the thing that vanishes isn't your knowledge base — it's the live state: what's done, what's mid-flight, which dead ends you already ruled out. I built a file-based handoff protocol so Claude Code and Codex can resume each other's half-finished work — a seven-section state file, updated on state transitions (not on a timer), with a hard rule to re-verify the last step before trusting it. Then the protocol's own rule — "no update in 2 hours means abandoned, take it" — flagged a training run with 19 hours left as up for grabs. The hook wasn't broken. It faithfully executed a rule I wrote. The fix was a phase-aware state machine.
It started with a plain complaint: I was halfway through a task, the quota ran out, and handing the rest to a fresh session felt like starting over. My first instinct was the usual one — the knowledge base isn't good enough, I need to store more. That instinct was wrong, and chasing it would have wasted a week. This is the story of the protocol I built instead, and of the day the protocol itself became the bug.
This is part of the AI Workflow series; it sits next to the hook I wrote to enforce rules I'd otherwise skip and the debate setup where I make two AIs argue. If you burn through quota often, the token burn-rate post is the upstream problem; this one is what to do when it happens anyway.
The thing that dies isn't your knowledge base — it's the live state
A knowledge base stores durable knowledge — the shape of a codebase, a deploy recipe, a lesson learned. That's exactly the stuff a fresh session can look up. It's not what you lose when a task dies halfway.
What you lose is the task's live state: which of the five sub-steps are done, which two approaches you've already ruled out and why, whether a background build is still running, and which command to run next. None of that is knowledge — it's the perishable specifics of this task, and it lives only in the session's context window. Quota runs out, the window evaporates, and a fresh session starts from a blank page even though the codebase, the memory, and the git history are all intact.
So growing the knowledge base never fixes the handoff. You can store ten times more durable knowledge and still lose every task mid-flight, because the thing that vanished was never in scope for a knowledge base. It was state. Once I named it that way, the fix was obvious: externalize the state to a file the way you'd externalize anything else that has to outlive a process.
The origin was almost embarrassing: I'd already done this by hand the day before, keeping a scratch file for a half-finished blog task that Codex was maintaining, complete with a commit hash on the "done" line. The protocol didn't invent anything. It just took the thing I already needed and stopped me from forgetting to do it.
Save on state transitions, not on a timer
The obvious design is autosave: dump the state every few minutes. It's also wrong, and it's wrong in a way that actively hurts.
A timer fires whenever it fires — which is usually mid-thought, halfway through a plan you haven't finished forming. The file it writes is a snapshot of confusion: a half-drafted approach, a command you were about to run but hadn't, a hypothesis you were 30 seconds from disproving. Whoever picks that up — a fresh session, or the other CLI — inherits your uncertainty as if it were a decision.
State-transition saves flip that. You write the file at moments when the state is, by construction, clean and resumable:
- after a discrete step completes (with evidence — a file:line, a commit, command output),
- right after you fire a long-running command (so the file records "this is in flight, here's how to collect it"),
- when you change direction (so the file never claims a dead approach is still live).

Every one of those is a moment when the state is a coherent thing you could hand to a stranger. A timer has no idea whether now is such a moment. That's the whole difference.
The handoff file: seven sections, and a rule never to trust the last step
The format is deliberately boring — boring is what survives being read by a cold-start agent that has none of your context. One file per task, ~/.claude/handoffs/<slug>.md, seven sections:
frontmatter # task / status / owner / updated / project / ticket
Goal # what "done" means, in one or two lines
Now # what's in flight right now — and how to collect it
Done # each item WITH evidence (file:line / commit / output)
Next # each item self-contained, runnable without the chat
Gotchas # traps the next owner would otherwise re-discover
Resume # the literal first command to run
Codex reaches the same directory through a symlink (~/.codex/handoffs → ~/.claude/handoffs), so both CLIs read and write one source of truth. Finished tasks move to archive/.
Two rules do most of the work. First, Done items are worthless without evidence — "fixed the parser" is a claim; "fixed the parser, parser.py:88, commit a1b2c3d, fixtures 11/11" is a state you can trust. Second, and this is the one that matters: the person taking over re-verifies the last Done item before building on it. You don't take the previous session's word for it. You run its evidence command and watch it pass. The four-step take-over is: read the whole file, set owner to yourself and bump updated (that's the lock against two drivers), re-run the last Done's evidence, then start at Next's first item.
Three gotchas that cost real time
Dogfooding this surfaced a few traps worth naming, because each one looked like success right up until it wasn't:
- A detached
codex execneeds its stdin redirected from/dev/null. Launched with an open, non-terminal stdin — which is exactly what a background launcher tends to hand it — Codex blocks onReading additional input from stdin..., waiting for an EOF that never comes, and never starts work. The process doesn't die, so it looks like it's running. The fingerprint is a log frozen at ~39 bytes with the handoff still un-claimed. Redirecting from/dev/nullgives it an immediate EOF; my first two tickets missed the trap purely by luck. - A background AI that hangs never notifies you. A crashed process exits and wakes you; a hung one just sits there. So every background
codex execticket gets a startup health check: about 45 seconds in, it confirms the log is still growing, because "it didn't fail" and "it's making progress" are different claims. - "The report says done" is not done. When a delegated run reports success, the orchestrator re-runs the acceptance command itself — the fixture, the round-trip, the file reconciliation. In my case Codex's numbers were all correct. I only know that because I checked. A report is a claim; acceptance is a command you run.
None of these are exotic. They're the difference between a protocol that works in the demo and one that survives a real overnight run.
Dogfooding it: Codex built its own staleness hooks
The first ticket the protocol ran was itself: I handed Codex the job of writing the staleness hooks that keep the protocol honest. Codex read the handoff, set owner to codex, worked through each step with evidence attached, and marked the task done — no human in the loop. I re-verified its fixtures independently: 5/5, 8/8, 11 passing. The same day, five overnight training tickets ran entirely handoff-driven, each one starting by verifying the previous owner's evidence before touching anything.
The hooks cover the three things people tend to forget: opening a handoff at the start (an open-nudge fires once when you've touched three different files with no active handoff), forgetting to update it mid-task (a stale-check nudges after 20 minutes), and forgetting to save before walking away (a stop-guard blocks the session from ending on a stale handoff, exactly once). That last one is the only script I let exit non-zero — refusing to stop is a big hammer, so it throttles hard and respects the stop-loop guard so it can never trap you.
The trap: my hook flagged a 19-hour training run as "up for grabs"
Here's where it turned on me.
One morning the session-start summary listed an overnight GPU training run as ⚠️ up for grabs. The run was fine — it had about 19 hours left to go. I went to look at the hook, expecting a bug. The hook was fine too. The culprit was a rule I'd written myself, in the protocol, in plain text:
owner is not you, and
updatedis more than 2 hours old → abandoned, take it.
That rule is correct for interactive work. A session that hasn't touched its handoff in two hours has almost certainly died. But a long-running command inverts the assumption: the whole point is that nobody touches the file for hours while the thing runs. The rule read "quiet for 2 hours" as "dead," when for a training run "quiet for 2 hours" means "working exactly as intended."

The hook didn't misfire. It executed a bad rule faithfully. That's the uncomfortable part of automating a doctrine: automation doesn't soften your mistakes, it amplifies them and puts them on a schedule. A rule you'd catch yourself bending in conversation becomes, once it's in a hook, a thing that fires at 8am against a job that's running perfectly.
There was a second tell I'd been ignoring. The stdin gotcha above had been sitting in my log for a month — every session "knew" it, right up until one session didn't. A lesson that lives only in a log is one unlucky session away from being un-learned.
The fix: a phase-aware state machine (and why the rule, not the hook, was the bug)
Fixing the hook would have been fixing the symptom. The bug was the rule, so the rule is what changed. "Stale" stopped being a pure function of the clock and became a function of phase.
The frontmatter grew a phase — running_external | waiting_user | blocked | working — and an expected_until timestamp (ISO 8601 with an explicit UTC offset, because "overdue" is ambiguous without one). The terminal states widened from a bare done to done | failed | cancelled | superseded. The session-start summary became phase-aware: a long-running job that hasn't hit its ETA shows as 🏃 running, not up for grabs; only a job past its expected_until gets flagged, and even then as "needs a look," not "take it." Jobs waiting_user or blocked went from invisible to visible.
The clearest sign that it worked is that the fixture output no longer contains the words "up for grabs" anywhere. Ownership went back to a human decision; owner demoted itself to a routing hint. The same 19-hour run that got flagged the day before now reads:
gx-fit-ladder-launch … 🏃 running (ETA 2026-07-11T17:55:00+08:00)
I also standardized every staleness check on one field — the frontmatter's updated. One hook had been reading the file's mtime while another read updated; on one real handoff, the two were two hours apart. Working this ticket, Codex ran an adversarial self-review and found two medium-severity bugs of its own — a counter that double-counted after a directory switch, and a contract mismatch on fractional timestamps. It wrote failing fixtures for both first, then fixed them.
The last mile of a lesson: logged ≠ documented ≠ templated ≠ enforced
The thread running through both days is that a lesson isn't done when you learn it. It has four stages, and most lessons stall at stage one:
- Logged — it's written down somewhere you could find it. (The
stdintrap lived here for a month.) - Documented — it's in the manual, where you'd look on purpose.
- Templated — the correct move is the default, baked into the ticket template so you can't forget it. (the ticket template now bundles all three:
< /dev/null,--add-dirfor the sandbox, and the 45-second startup check.) - Enforced — a hook makes the wrong move impossible, or at least fails noisily.
A log entry every session re-reads is not the same as a template every session uses. The stdin bug was "logged" for a month and still bit me, because logged and enforced are three whole stages apart. The false-takeover bug was the same failure mode a level up: writing a rule down doesn't make it correct, and automating a bad rule only makes it fail faster.
Conclusion: the checklist
If you hand work between AI sessions or CLIs, this is the short version:
- Externalize live state, not just knowledge. The knowledge base survives the session; the task's live state doesn't. Write it to a file.
- Save on state transitions, never on a timer. After a step, after firing a long command, on a direction change — never mid-thought.
- Evidence or it didn't happen. Every
Doneitem carries a file:line, commit, or output. The next owner re-runs it before trusting it. - A background AI that hangs won't tell you. Pair every detached run with a startup freshness check, and redirect its stdin from
/dev/null. - Fix the rule before the hook. When automation does something dumb, check whether it's faithfully executing a bad rule. Usually it is.
- Don't just log a lesson — enforce it. Logged is stage one of four. If it matters, get it into the template or the hook — otherwise you're one unlucky session away from relearning it.
FAQ
- How do you resume an AI coding session after it runs out of quota or context?
- Don't rely on the chat history or the knowledge base — both miss the live state (what's done, what's mid-flight, which dead ends you already ruled out). Write that state to a file on every state transition, so any fresh session — or a different CLI — can read it and keep going. Even if the old transcript is still resumable on disk, a fresh session won't reconstruct the live state for you; the handoff file will.
- Can Claude Code and Codex hand off work to each other?
- Yes, if they share a state file. I keep handoff files in one directory that both read and write (Codex reaches it through a symlink), with a fixed seven-section format. Either CLI can pick up a task the other left half-finished: read the file, take ownership, re-verify the last completed step, then continue from the next item.
- Should an AI agent autosave its state on a timer?
- No. A timer catches you mid-thought and saves a half-formed plan that misleads whoever picks it up. Save on state transitions instead — after a step completes, after you fire a long-running command, when you change direction. What you save is then always a clean, resumable state, never a snapshot of confusion.
- What's the difference between a handoff file and a memory or knowledge base?
- A knowledge base stores durable knowledge — things that stay true across tasks. A handoff file stores live state — the perishable specifics of one task in flight. Growing the knowledge base never fixes a broken handoff, because the thing that vanished when the session died was never knowledge; it was state.
Read next
- 2026-05-19[Claude Code] Rules I'd Skip, Hooks I Can't — I Wrote a Hook That Blocks My Own Blog Commits
I had a rule called 'fact-check before publishing.' I still shipped three fabrications. The problem wasn't the rule — it was where I put it. This is how I promoted it from skill to hook: a small script guarding the moment I press 'send,' so I can't even try without verification.
- 2026-02-26[Dev Workflow] I Made Two AIs Argue. The Disagreements Are the Point.
A custom /debate command that pits Codex CLI against Gemini CLI on architecture, code, and decisions. Different training data, different blind spots — and the disagreements between them are usually the most useful output.
- 2026-05-05How a zh-TW Linter Found 128 Mainland-China Drift in My Own Writing
I ran sysprog21/zhtw-mcp across 72 of my Traditional Chinese articles. Three sweeps, 128 cross-strait substitutions across 42 files. The real takeaway wasn't the count — it was discovering my blindspot isn't 'I don't know the right Taiwanese term,' it's 'when a Mainland term shows up I don't auto-doubt it.'
- 2026-04-13Claude Code Burning Through Tokens? 8 Fixes to Make Sessions Last 10x Longer
You just started using Claude Code and the context window keeps filling up. Here's where the tokens actually go, what you can do about it, and how to make Claude remember things without re-reading everything.
Don't miss the next one
Subscribe, and you won't.
One-click unsubscribe anytime.