How I stopped Codex quota resets from interrupting long-running agent workflows

QuotaSentry turns a known near-limit Codex window into a local wait state, so guarded work does not depend on a human returning to type “continue.”

I kept having to return and type continue.

The pattern was familiar. A long Codex task would make useful progress, reach the end of a quota window, and stop. I could wait for the reset, but that was not the same as pausing a process. I had to notice the reset, return to the session, decide whether the task still made sense, and add another instruction to get it moving again.

That made the workflow depend on me babysitting a clock. The interruption was small in isolation, but repeated interruptions changed the shape of the work. A task that should have had one execution flow became a series of handoffs back to myself.

QuotaSentry came from a narrower question: when quota state is known, can the next guarded action wait before it consumes the last part of the window? The goal is not to make quota invisible. It is to turn a predictable boundary into an explicit wait state instead of a manual restart.

A quota reset is not a pause

Waiting is not the only cost of a quota limit. The more disruptive part is the attention switch around it: remembering to come back, looking at an old session, and adding a new instruction in the middle of a task. The agent may still have the visible conversation, but the execution is no longer one continuous path. A person has become part of the control loop.

That distinction changes the design target. A quota guard cannot undo a request that a provider has already rejected. It also cannot recreate private model state. What it can do is avoid starting a future guarded prompt or tool call when a fresh quota reading says that the configured boundary has already been reached.

This is admission control, not recovery. The best time to avoid a quota-wall interruption is before the next unit of work starts, while there is still a known reset time to wait through.

The design contract: conservative admission control

QuotaSentry watches the 300-minute Codex quota window. By default, it considers the window blocked at 95% used (usedPercent >= 95) and waits until resetsAt plus a 60-second buffer. The buffer avoids treating a reported reset timestamp as an exact promise that capacity is immediately usable.

The decision rules are deliberately small:

fresh blocked state  -> wait until reset plus buffer
fresh open state     -> allow work
stale or unknown     -> allow work
manual bypass set    -> allow work

The third line is the important one. A quota guard has incomplete information: a source can time out, return malformed data, report a missing reset time, or leave a cache entry behind after the daemon has stopped. Blocking in any of those cases would make the guard itself the reason work could not continue.

That is why QuotaSentry uses a fail-open rule. False blocking is worse than missing a wait: a user can decide how to handle an exhausted window, but they should not be trapped by a local tool that is unsure of its own state. QUOTA_SENTRY_DISABLE=1 is also an explicit escape hatch for a session where the user wants to bypass the guard.

Separate observation from enforcement

The implementation separates quota observation from the moment that needs to make an admission decision.

First, a background daemon reads live quota state and writes a small decision record to ~/.cache/quota-sentry/state.json. Its primary source is codex app-server --stdio, using account/rateLimits/read; when automatic source selection cannot use that path, it falls back to CodexBar. The daemon owns live polling because it can tolerate slow sources, retries, and failures without placing that work in an interactive hook.

Second, the local cache carries only the information the guard needs: whether the state is open, blocked, or unknown; when it was updated; the reset time; and the blocked-until time. It is an inspectable boundary between a background observer and the Codex process that is about to do work.

Finally, global Codex hooks read that cached state synchronously. SessionStart starts the daemon quietly. UserPromptSubmit makes sure the daemon is running, then checks cached state. PreToolUse reads cached state only before a tool call. Neither hook path performs a live quota lookup.

That split is the point of the system. A hook should be quick and predictable; a quota source may be slow or unavailable. Calling a source from every hook would create a new failure path exactly where the workflow needs a simple answer. Cache-only hooks can either see a fresh blocked decision and wait, or see anything else and let the work proceed.

The sharp edges are the product

The straightforward version of this project would be a timer. The useful version has to make the uncertain cases unsurprising.

The daemon polls every five minutes when usage is comfortably below the boundary. It increases its cadence to every 60 seconds at 85% used and every 30 seconds at 93%. That makes the state more current near the threshold without making prompt and tool hooks wake a live quota source on every action.

Freshness matters just as much as the percentage. A cached blocked state is used only while it is recent and its blockedUntil time remains in the future. Missing, stale, malformed, or unavailable data fails open. Once the reset is in the past, the guard does not keep waiting on an old record.

Hook output is also part of correctness. Codex can surface command output in its interface after a long wait, so noisy stdout or stderr from a guard would turn a useful pause into accumulated clutter. Installed hook paths stay quiet and do not open the controlling terminal. A manual guard invocation can emit one human-readable wait notice outside the hook path, where that feedback is actually useful.

The installer has a similarly boring job: merge QuotaSentry into existing global hook configuration without replacing unrelated hooks. Its generated hook commands are single invocations rather than shell pipelines. These constraints do not make the project more impressive in a demo, but they are what let a guard coexist with a real development environment.

What I verified

I did not want this behavior to depend on a happy-path reading of a quota response. The current repository has 42 unit tests and 11 autonomous scenarios. Those tests cover fresh versus stale decisions, threshold and reset behavior, quiet waits, app-server-to-CodexBar fallback, failed sources that fail open, cache-only hook behavior, and hook configuration merging.

The autonomous harness uses synthetic codex and codexbar binaries for the edge cases so it can exercise short reset windows, failed sources, and hook behavior without repeatedly consuming a real quota window. It also has a bounded live source smoke path for checking that a real quota source can still be read.

That is evidence for the implementation contract, not a reliability percentage. It does not prove that every Codex version, account state, or hook environment behaves identically. It does make the choices around stale state, source failure, reset timing, and hook output concrete enough to test.

A small control plane for a recurring interruption

QuotaSentry is intentionally Codex-only and local-only. It is not a hosted service, a generic agent orchestrator, or a substitute for provider-side quota controls. Its job is narrower: make a known local quota boundary predictable before future guarded work begins.

The current setup runs from a repository checkout:

git clone https://github.com/dhruvil009/QuotaSentry.git
cd QuotaSentry
./scripts/quota-sentry install-hook

Restart Codex if the current session does not pick up the installed global hooks. From there, the daemon and cache provide the guardrail; the user still owns the threshold, bypass, and decision to keep working.

The larger lesson is not that coding agents should run unattended forever. It is that longer-running workflows expose routine operational edges that people should not have to babysit by hand. A small, conservative control plane can preserve execution continuity without pretending that uncertainty—or quota limits—have disappeared.

Read the implementation and current setup details in the QuotaSentry repository.