
Track Codex Retries and Token Usage with Model Request Hooks
Codex’s new PreModelRequest and PostModelRequest hooks expose model-call lifecycle data, including retries, status, and reported token usage. Learn how to register an async logging hook, account for cancelled turns, and check build availability, plus smaller security and workflow updates.
Chapter 1
Model Request Hooks Land
Ethan Park
If you've ever tried to work out why one Codex turn burned far more tokens than you expected, you've probably hit the same wall. Hooks could see your tools and your session boundaries, but not the model calls themselves. That changed on main on October 18. And thanks to Jellypod for helping make this daily show a reality. I'm Ethan.
Maya
And I'm Maya. Today's topic is pull request 55144, which adds two new lifecycle hooks, PreModelRequest and PostModelRequest. They fire around each model request attempt, and that includes automatic retries. One note on availability: the commit landed on main, and the 0.164.0 alpha builds are the line to watch. The stable release right now is 0.163.0, so check what you're actually running before you go looking for these.
Chapter 2
Wiring Up PreModelRequest and PostModelRequest
Ethan Park
Here's the gap this closes. Before this, you had PreToolUse, PostToolUse, SessionStart, SessionEnd, and UserPromptSubmit. Everything between your prompt and the tool call was inside the client. A transient retry, per call latency, a subagent dispatching its own inference, the exact token spend. None of that was visible to a script. Now your hook receives JSON on stdin with the model, the session ID, the turn ID, and optional subagent context when the request comes from a subagent. PostModelRequest adds a status, which is completed, failed, or cancelled. If it failed, you get error diagnostics. And when the Responses API reports usage, you get the token breakdown: prompt, completion, reasoning, and total.
Maya
The design choice I want to stress is that these hooks are observation only. PreToolUse can return a decision of block and stop a tool. UserPromptSubmit can inject context. These two can't do either. Whatever your script prints is treated as diagnostic data, and any control directive, any block attempt, any prompt override, is ignored by design. That's actually a feature for telemetry. A monitoring script or a plugin you didn't write can't corrupt model context or break a turn by accident. But it also means you shouldn't try to build a guardrail here. If you want to block something, that still belongs in PreToolUse.
Ethan Park
Now, how you wire it up. You register hooks in a hooks.json file, either at the project root under .codex, or globally in your home directory under .codex. Event names go at the root of the file. So a PreModelRequest entry holds a hooks array with a command type, say node .codex/hooks/log-model-start.mjs. Then PostModelRequest gets its own entry, for example python3 scripts/track_tokens.py, and you add async set to true on that one. You can also register them in config.toml, using double bracket tables named hooks dot PreModelRequest and hooks dot PostModelRequest.
Maya
That async flag matters more than it looks, so let me walk through it. With a synchronous PostModelRequest hook, Codex waits for your process to exit before it dispatches any tools that turn produced. So imagine a hook that posts to an HTTP logging endpoint, and that endpoint takes two seconds. Every single model response now waits two seconds before its tool calls start. With async true, streaming tool dispatch carries on and your logger runs alongside it. My rule of thumb: if the hook does anything over the network, make it async, or append to a local file and ship the batch later. Keep synchronous for cases where you truly need the record written before tools run.
Ethan Park
Two more behaviors to know. First, interrupted turns. If you interrupt mid turn, or steer with a follow up, PostModelRequest still fires, with a status of cancelled, and it records partial token consumption where the Responses API stream reported it. So if your dashboards only count completed, you'll undercount real spend. Second, memory consolidation. Codex applies hook filtering around internal memory consolidation, so requests from those background passes can be excluded or scoped separately and don't inflate your per turn numbers. The source here is the commit description, so I'd verify exactly how that filtering behaves on your build before you trust a cost report built on it.
Maya
And to check your setup, the new events show up in the TUI hooks browser, in the protocol schemas, and in the generated clients. So if you run the TypeScript or Python SDK, regenerate and you'll see the types. A practical first step: register just PostModelRequest, async, writing one JSON line per call to a local file. Run a normal session, then look at the failed and cancelled rows. That alone tells you how often retries are happening.
Ethan Park
A few smaller items. Pull request 54761 on October 17 upgrades the zip library to 9.0.0, and plugin extraction now filters dangerous permission bits. No setuid, setgid, or sticky bits, while normal executable mode, 0o755, is preserved. That closes a local privilege escalation path from a plugin archive you didn't author.
Maya
Also from 0.163.0, which is the current stable. Independent read only tools, so skill, memory, and conversation history lookups, can now run in parallel. Resuming a saved thread restores its permissions and network approvals, while respecting explicit overrides and current managed policy. And the network proxy now checks hostname authorization before a DNS lookup, and rejects duplicate Host headers. Finally, on the app server side, pull request 55283 avoids cloning events in the thread listener, and 55274 stabilizes daemon startup tests, including warming Intel packages under Rosetta on Apple Silicon for up to 180 seconds. Those are internal, so nothing for you to change.
Ethan Park
So the takeaway: start with one async PostModelRequest hook that logs status and token usage, and you get retry and cost visibility without sniffing proxy traffic. Just keep it out of the critical path. We'll be back tomorrow.