1
00:00:00,320 --> 00:00:05,540
If you've ever tried to work out why one
Codex turn burned far more tokens than you

2
00:00:05,600 --> 00:00:08,460
expected, you've probably hit the same
wall.

3
00:00:09,240 --> 00:00:13,420
Hooks could see your tools and your
session boundaries, but not the model calls

4
00:00:13,460 --> 00:00:14,000
themselves.

5
00:00:14,860 --> 00:00:17,660
That changed on main on October 18.

6
00:00:18,980 --> 00:00:22,400
And thanks to Jellypod for helping make
this daily show a reality.

7
00:00:23,120 --> 00:00:23,640
I'm Ethan.

8
00:00:24,307 --> 00:00:25,147
And I'm Maya.

9
00:00:25,647 --> 00:00:30,767
Today's topic is pull request 55144, which
adds two new lifecycle hooks,

10
00:00:31,147 --> 00:00:33,607
PreModelRequest and PostModelRequest.

11
00:00:34,287 --> 00:00:38,427
They fire around each model request
attempt, and that includes automatic retries.

12
00:00:39,067 --> 00:00:45,047
One note on availability: the commit
landed on main, and the 0.164.0 alpha builds

13
00:00:45,087 --> 00:00:45,887
are the line to watch.

14
00:00:46,527 --> 00:00:51,627
The stable release right now is 0.163.0,
so check what you're actually running

15
00:00:51,667 --> 00:00:52,747
before you go looking for these.

16
00:00:53,718 --> 00:00:55,058
Here's the gap this closes.

17
00:00:55,738 --> 00:01:00,718
Before this, you had PreToolUse,
PostToolUse, SessionStart,

18
00:01:01,018 --> 00:01:03,518
SessionEnd, and UserPromptSubmit.

19
00:01:04,618 --> 00:01:08,118
Everything between your prompt and the
tool call was inside the client.

20
00:01:08,838 --> 00:01:13,978
A transient retry, per call latency, a
subagent dispatching its own inference,

21
00:01:14,358 --> 00:01:15,678
the exact token spend.

22
00:01:16,358 --> 00:01:17,978
None of that was visible to a script.

23
00:01:19,058 --> 00:01:23,658
Now your hook receives JSON on stdin with
the model, the session ID,

24
00:01:23,958 --> 00:01:28,638
the turn ID, and optional subagent context
when the request comes from a subagent.

25
00:01:29,398 --> 00:01:33,458
PostModelRequest adds a status, which is
completed, failed,

26
00:01:33,638 --> 00:01:34,318
or cancelled.

27
00:01:34,978 --> 00:01:37,138
If it failed, you get error diagnostics.

28
00:01:37,978 --> 00:01:42,698
And when the Responses API reports usage,
you get the token breakdown: prompt,

29
00:01:42,998 --> 00:01:45,058
completion, reasoning, and total.

30
00:01:45,748 --> 00:01:49,768
The design choice I want to stress is that
these hooks are observation only.

31
00:01:50,348 --> 00:01:54,528
PreToolUse can return a decision of block
and stop a tool.

32
00:01:55,248 --> 00:01:57,548
UserPromptSubmit can inject context.

33
00:01:58,208 --> 00:01:59,288
These two can't do either.

34
00:01:59,768 --> 00:02:04,128
Whatever your script prints is treated as
diagnostic data, and any control

35
00:02:04,188 --> 00:02:09,008
directive, any block attempt, any prompt
override, is ignored by design.

36
00:02:09,607 --> 00:02:11,288
That's actually a feature for telemetry.

37
00:02:11,868 --> 00:02:16,548
A monitoring script or a plugin you didn't
write can't corrupt model context or

38
00:02:16,788 --> 00:02:17,968
break a turn by accident.

39
00:02:18,528 --> 00:02:21,108
But it also means you shouldn't try to
build a guardrail here.

40
00:02:21,628 --> 00:02:25,008
If you want to block something, that still
belongs in PreToolUse.

41
00:02:25,845 --> 00:02:27,525
Now, how you wire it up.

42
00:02:28,165 --> 00:02:33,985
You register hooks in a hooks.json file,
either at the project root under .codex,

43
00:02:34,505 --> 00:02:37,185
or globally in your home directory under
.codex.

44
00:02:38,085 --> 00:02:40,105
Event names go at the root of the file.

45
00:02:40,625 --> 00:02:44,845
So a PreModelRequest entry holds a hooks
array with a command type,

46
00:02:45,305 --> 00:02:46,345
say node

47
00:02:46,625 --> 00:02:52,825
.codex/hooks/log-model-start.mjs.

48
00:02:53,485 --> 00:02:58,285
Then PostModelRequest gets its own entry,
for example python3

49
00:02:58,585 --> 00:03:05,345
scripts/track_tokens.py, and you add async
set to true on that one.

50
00:03:06,005 --> 00:03:10,885
You can also register them in config.toml,
using double bracket tables named hooks

51
00:03:10,985 --> 00:03:14,365
dot PreModelRequest and hooks dot
PostModelRequest.

52
00:03:14,905 --> 00:03:18,385
That async flag matters more than it
looks, so let me walk through it.

53
00:03:19,185 --> 00:03:24,205
With a synchronous PostModelRequest hook,
Codex waits for your process to exit

54
00:03:24,585 --> 00:03:27,305
before it dispatches any tools that turn
produced.

55
00:03:27,925 --> 00:03:34,325
So imagine a hook that posts to an HTTP
logging endpoint, and that endpoint takes

56
00:03:34,385 --> 00:03:34,905
two seconds.

57
00:03:35,625 --> 00:03:40,105
Every single model response now waits two
seconds before its tool calls start.

58
00:03:40,945 --> 00:03:45,745
With async true, streaming tool dispatch
carries on and your logger runs alongside

59
00:03:45,785 --> 00:03:45,945
it.

60
00:03:47,045 --> 00:03:49,945
My rule of thumb: if the hook does
anything over the network,

61
00:03:50,445 --> 00:03:53,965
make it async, or append to a local file
and ship the batch later.

62
00:03:54,585 --> 00:03:59,585
Keep synchronous for cases where you truly
need the record written before tools run.

63
00:04:00,368 --> 00:04:01,768
Two more behaviors to know.

64
00:04:02,368 --> 00:04:04,248
First, interrupted turns.

65
00:04:04,968 --> 00:04:10,468
If you interrupt mid turn, or steer with a
follow up, PostModelRequest still fires,

66
00:04:10,808 --> 00:04:15,808
with a status of cancelled, and it records
partial token consumption where the

67
00:04:15,868 --> 00:04:17,887
Responses API stream reported it.

68
00:04:18,648 --> 00:04:23,808
So if your dashboards only count
completed, you'll undercount real spend.

69
00:04:24,828 --> 00:04:26,768
Second, memory consolidation.

70
00:04:27,908 --> 00:04:31,708
Codex applies hook filtering around
internal memory consolidation,

71
00:04:32,248 --> 00:04:37,888
so requests from those background passes
can be excluded or scoped separately and

72
00:04:37,948 --> 00:04:39,848
don't inflate your per turn numbers.

73
00:04:41,028 --> 00:04:46,468
The source here is the commit description,
so I'd verify exactly how that filtering

74
00:04:46,508 --> 00:04:50,568
behaves on your build before you trust a
cost report built on it.

75
00:04:50,990 --> 00:04:55,530
And to check your setup, the new events
show up in the TUI hooks browser,

76
00:04:56,110 --> 00:04:59,330
in the protocol schemas, and in the
generated clients.

77
00:04:59,990 --> 00:05:05,010
So if you run the TypeScript or Python
SDK, regenerate and you'll see the types.

78
00:05:05,750 --> 00:05:10,690
A practical first step: register just
PostModelRequest, async,

79
00:05:11,150 --> 00:05:14,030
writing one JSON line per call to a local
file.

80
00:05:14,810 --> 00:05:18,510
Run a normal session, then look at the
failed and cancelled rows.

81
00:05:19,190 --> 00:05:22,010
That alone tells you how often retries are
happening.

82
00:05:22,673 --> 00:05:23,873
A few smaller items.

83
00:05:24,393 --> 00:05:30,153
Pull request 54761 on October 17 upgrades
the zip library to

84
00:05:30,433 --> 00:05:35,113
9.0.0, and plugin extraction now filters
dangerous permission bits.

85
00:05:35,673 --> 00:05:40,553
No setuid, setgid, or sticky bits, while
normal executable mode,

86
00:05:41,333 --> 00:05:43,353
0o755, is preserved.

87
00:05:44,033 --> 00:05:48,413
That closes a local privilege escalation
path from a plugin archive you didn't

88
00:05:48,493 --> 00:05:48,733
author.

89
00:05:49,567 --> 00:05:53,627
Also from 0.163.0, which is the current
stable.

90
00:05:54,387 --> 00:05:59,907
Independent read only tools, so skill,
memory, and conversation history lookups,

91
00:06:00,387 --> 00:06:01,707
can now run in parallel.

92
00:06:02,467 --> 00:06:06,387
Resuming a saved thread restores its
permissions and network approvals,

93
00:06:06,807 --> 00:06:10,347
while respecting explicit overrides and
current managed policy.

94
00:06:11,107 --> 00:06:15,627
And the network proxy now checks hostname
authorization before a DNS lookup,

95
00:06:16,107 --> 00:06:17,767
and rejects duplicate Host headers.

96
00:06:18,627 --> 00:06:24,847
Finally, on the app server side, pull
request 55283 avoids cloning events in the

97
00:06:24,867 --> 00:06:30,187
thread listener, and 55274 stabilizes
daemon startup tests,

98
00:06:30,507 --> 00:06:35,787
including warming Intel packages under
Rosetta on Apple Silicon for up to 180

99
00:06:35,827 --> 00:06:36,207
seconds.

100
00:06:36,847 --> 00:06:39,247
Those are internal, so nothing for you to
change.

101
00:06:39,912 --> 00:06:46,672
So the takeaway: start with one async
PostModelRequest hook that logs status and

102
00:06:46,792 --> 00:06:52,912
token usage, and you get retry and cost
visibility without sniffing proxy traffic.

103
00:06:53,632 --> 00:06:55,432
Just keep it out of the critical path.

104
00:06:56,032 --> 00:06:56,872
We'll be back tomorrow.

