SpyBara
Go Premium Account
2026
23 Mar 2026, 18:22
14 May 2026, 21:00 14 May 2026, 07:00 13 May 2026, 00:57 12 May 2026, 01:59 11 May 2026, 18:00 7 May 2026, 20:02 7 May 2026, 17:08 5 May 2026, 23:00 2 May 2026, 06:45 2 May 2026, 00:48 1 May 2026, 18:29 30 Apr 2026, 18:36 29 Apr 2026, 12:40 29 Apr 2026, 00:50 25 Apr 2026, 06:37 25 Apr 2026, 00:42 24 Apr 2026, 18:20 24 Apr 2026, 12:28 23 Apr 2026, 18:31 23 Apr 2026, 12:28 23 Apr 2026, 00:46 22 Apr 2026, 18:29 22 Apr 2026, 00:42 21 Apr 2026, 18:29 21 Apr 2026, 12:30 21 Apr 2026, 06:45 20 Apr 2026, 18:26 20 Apr 2026, 06:53 18 Apr 2026, 18:18 17 Apr 2026, 00:44 16 Apr 2026, 18:31 16 Apr 2026, 00:46 15 Apr 2026, 18:31 15 Apr 2026, 06:44 14 Apr 2026, 18:31 14 Apr 2026, 12:29 13 Apr 2026, 18:37 13 Apr 2026, 00:44 12 Apr 2026, 06:38 10 Apr 2026, 18:23 9 Apr 2026, 00:33 8 Apr 2026, 18:32 8 Apr 2026, 00:40 7 Apr 2026, 00:40 2 Apr 2026, 18:23 31 Mar 2026, 06:35 31 Mar 2026, 00:39 28 Mar 2026, 06:26 28 Mar 2026, 00:36 27 Mar 2026, 18:23 27 Mar 2026, 00:39 26 Mar 2026, 18:27 25 Mar 2026, 18:24 23 Mar 2026, 18:22 20 Mar 2026, 00:35 18 Mar 2026, 12:23 18 Mar 2026, 00:36 17 Mar 2026, 18:24 17 Mar 2026, 00:33 16 Mar 2026, 18:25 16 Mar 2026, 12:23 14 Mar 2026, 00:32 13 Mar 2026, 18:15 13 Mar 2026, 00:34 11 Mar 2026, 00:31 9 Mar 2026, 00:34 8 Mar 2026, 18:10 8 Mar 2026, 00:35 7 Mar 2026, 18:10 7 Mar 2026, 06:14 7 Mar 2026, 00:33 6 Mar 2026, 00:38 5 Mar 2026, 18:41 5 Mar 2026, 06:22 5 Mar 2026, 00:34 4 Mar 2026, 18:18 4 Mar 2026, 06:20 3 Mar 2026, 18:20 3 Mar 2026, 00:35 27 Feb 2026, 18:15 24 Feb 2026, 06:27 24 Feb 2026, 00:33 23 Feb 2026, 18:27 21 Feb 2026, 00:33 20 Feb 2026, 12:16 19 Feb 2026, 20:53 19 Feb 2026, 20:37
24 Apr 2026, 18:20
14 May 2026, 21:00 14 May 2026, 07:00 13 May 2026, 00:57 12 May 2026, 01:59 11 May 2026, 18:00 7 May 2026, 20:02 7 May 2026, 17:08 5 May 2026, 23:00 2 May 2026, 06:45 2 May 2026, 00:48 1 May 2026, 18:29 30 Apr 2026, 18:36 29 Apr 2026, 12:40 29 Apr 2026, 00:50 25 Apr 2026, 06:37 25 Apr 2026, 00:42 24 Apr 2026, 18:20 24 Apr 2026, 12:28 23 Apr 2026, 18:31 23 Apr 2026, 12:28 23 Apr 2026, 00:46 22 Apr 2026, 18:29 22 Apr 2026, 00:42 21 Apr 2026, 18:29 21 Apr 2026, 12:30 21 Apr 2026, 06:45 20 Apr 2026, 18:26 20 Apr 2026, 06:53 18 Apr 2026, 18:18 17 Apr 2026, 00:44 16 Apr 2026, 18:31 16 Apr 2026, 00:46 15 Apr 2026, 18:31 15 Apr 2026, 06:44 14 Apr 2026, 18:31 14 Apr 2026, 12:29 13 Apr 2026, 18:37 13 Apr 2026, 00:44 12 Apr 2026, 06:38 10 Apr 2026, 18:23 9 Apr 2026, 00:33 8 Apr 2026, 18:32 8 Apr 2026, 00:40 7 Apr 2026, 00:40 2 Apr 2026, 18:23 31 Mar 2026, 06:35 31 Mar 2026, 00:39 28 Mar 2026, 06:26 28 Mar 2026, 00:36 27 Mar 2026, 18:23 27 Mar 2026, 00:39 26 Mar 2026, 18:27 25 Mar 2026, 18:24 23 Mar 2026, 18:22 20 Mar 2026, 00:35 18 Mar 2026, 12:23 18 Mar 2026, 00:36 17 Mar 2026, 18:24 17 Mar 2026, 00:33 16 Mar 2026, 18:25 16 Mar 2026, 12:23 14 Mar 2026, 00:32 13 Mar 2026, 18:15 13 Mar 2026, 00:34 11 Mar 2026, 00:31 9 Mar 2026, 00:34 8 Mar 2026, 18:10 8 Mar 2026, 00:35 7 Mar 2026, 18:10 7 Mar 2026, 06:14 7 Mar 2026, 00:33 6 Mar 2026, 00:38 5 Mar 2026, 18:41 5 Mar 2026, 06:22 5 Mar 2026, 00:34 4 Mar 2026, 18:18 4 Mar 2026, 06:20 3 Mar 2026, 18:20 3 Mar 2026, 00:35 27 Feb 2026, 18:15 24 Feb 2026, 06:27 24 Feb 2026, 00:33 23 Feb 2026, 18:27 21 Feb 2026, 00:33 20 Feb 2026, 12:16 19 Feb 2026, 20:53 19 Feb 2026, 20:37
Thu 2 18:23 Tue 7 00:40 Wed 8 00:40 Wed 8 18:32 Thu 9 00:33 Fri 10 18:23 Sun 12 06:38 Mon 13 00:44 Mon 13 18:37 Tue 14 12:29 Tue 14 18:31 Wed 15 06:44 Wed 15 18:31 Thu 16 00:46 Thu 16 18:31 Fri 17 00:44 Sat 18 18:18 Mon 20 06:53 Mon 20 18:26 Tue 21 06:45 Tue 21 12:30 Tue 21 18:29 Wed 22 00:42 Wed 22 18:29 Thu 23 00:46 Thu 23 12:28 Thu 23 18:31 Fri 24 12:28 Fri 24 18:20 Sat 25 00:42 Sat 25 06:37 Wed 29 00:50 Wed 29 12:40 Thu 30 18:36

hooks.md +486 −0 added

Details

1# Hooks

2 

3Experimental. Hooks are under active development. Windows support temporarily

4disabled.

5 

6Hooks are an extensibility framework for Codex. They allow

7you to inject your own scripts into the agentic loop, enabling features such as:

8 

9- Send the conversation to a custom logging/analytics engine

10- Scan your team's prompts to block accidentally pasting API keys

11- Summarize conversations to create persistent memories automatically

12- Run a custom validation check when a conversation turn stops, enforcing standards

13- Customize prompting when in a certain directory

14 

15Hooks are behind a feature flag in `config.toml`:

16 

17```toml

18[features]

19codex_hooks = true

20```

21 

22Runtime behavior to keep in mind:

23 

24- Matching hooks from multiple files all run.

25- Multiple matching command hooks for the same event are launched concurrently,

26 so one hook can’t prevent another matching hook from starting.

27- `PreToolUse`, `PermissionRequest`, `PostToolUse`, `UserPromptSubmit`, and

28 `Stop` run at turn scope.

29- Hooks are currently disabled on Windows.

30 

31## Where Codex looks for hooks

32 

33Codex discovers `hooks.json` next to active config layers.

34 

35In practice, the two most useful locations are:

36 

37- `~/.codex/hooks.json`

38- `<repo>/.codex/hooks.json`

39 

40If more than one `hooks.json` file exists, Codex loads all matching hooks.

41Higher-precedence config layers don’t replace lower-precedence hooks.

42 

43Project-local hooks load only when the project `.codex/` layer is trusted. In

44untrusted projects, Codex still loads user and system hooks from their own

45active config layers.

46 

47## Config shape

48 

49Hooks are organized in three levels:

50 

51- A hook event such as `PreToolUse`, `PostToolUse`, or `Stop`

52- A matcher group that decides when that event matches

53- One or more hook handlers that run when the matcher group matches

54 

55```json

56{

57 "hooks": {

58 "SessionStart": [

59 {

60 "matcher": "startup|resume",

61 "hooks": [

62 {

63 "type": "command",

64 "command": "python3 ~/.codex/hooks/session_start.py",

65 "statusMessage": "Loading session notes"

66 }

67 ]

68 }

69 ],

70 "PreToolUse": [

71 {

72 "matcher": "Bash",

73 "hooks": [

74 {

75 "type": "command",

76 "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/pre_tool_use_policy.py\"",

77 "statusMessage": "Checking Bash command"

78 }

79 ]

80 }

81 ],

82 "PermissionRequest": [

83 {

84 "matcher": "Bash",

85 "hooks": [

86 {

87 "type": "command",

88 "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/permission_request.py\"",

89 "statusMessage": "Checking approval request"

90 }

91 ]

92 }

93 ],

94 "PostToolUse": [

95 {

96 "matcher": "Bash",

97 "hooks": [

98 {

99 "type": "command",

100 "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/post_tool_use_review.py\"",

101 "statusMessage": "Reviewing Bash output"

102 }

103 ]

104 }

105 ],

106 "UserPromptSubmit": [

107 {

108 "hooks": [

109 {

110 "type": "command",

111 "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/user_prompt_submit_data_flywheel.py\""

112 }

113 ]

114 }

115 ],

116 "Stop": [

117 {

118 "hooks": [

119 {

120 "type": "command",

121 "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/stop_continue.py\"",

122 "timeout": 30

123 }

124 ]

125 }

126 ]

127 }

128}

129```

130 

131Notes:

132 

133- `timeout` is in seconds.

134- `timeoutSec` is also accepted as an alias.

135- If `timeout` is omitted, Codex uses `600` seconds.

136- `statusMessage` is optional.

137- Commands run with the session `cwd` as their working directory.

138- For repo-local hooks, prefer resolving from the git root instead of using a

139 relative path such as `.codex/hooks/...`. Codex may be started from a

140 subdirectory, and a git-root-based path keeps the hook location stable.

141 

142## Matcher patterns

143 

144The `matcher` field is a regex string that filters when hooks fire. Use `"*"`,

145`""`, or omit `matcher` entirely to match every occurrence of a supported

146event.

147 

148Only some current Codex events honor `matcher`:

149 

150| Event | What `matcher` filters | Notes |

151| --- | --- | --- |

152| `PermissionRequest` | tool name | Current Codex runtime only emits `Bash`. |

153| `PostToolUse` | tool name | Current Codex runtime only emits `Bash`. |

154| `PreToolUse` | tool name | Current Codex runtime only emits `Bash`. |

155| `SessionStart` | start source | Current runtime values are `startup` and `resume`. |

156| `UserPromptSubmit` | not supported | Any configured `matcher` is ignored for this event. |

157| `Stop` | not supported | Any configured `matcher` is ignored for this event. |

158 

159Examples:

160 

161- `Bash`

162- `startup|resume`

163- `Edit|Write`

164 

165That last example is still a valid regex, but current Codex `PreToolUse` and

166`PostToolUse` events only emit `Bash`, so it won’t match anything today.

167 

168## Common input fields

169 

170Every command hook receives one JSON object on `stdin`.

171 

172These are the shared fields you will usually use:

173 

174| Field | Type | Meaning |

175| --- | --- | --- |

176| `session_id` | `string` | Current session or thread id. |

177| `transcript_path` | `string | null` | Path to the session transcript file, if any |

178| `cwd` | `string` | Working directory for the session |

179| `hook_event_name` | `string` | Current hook event name |

180| `model` | `string` | Active model slug |

181 

182Turn-scoped hooks list `turn_id` in their event-specific tables.

183 

184If you need the full wire format, see [Schemas](#schemas).

185 

186## Common output fields

187 

188`SessionStart`, `UserPromptSubmit`, and `Stop` support these shared JSON

189fields:

190 

191```json

192{

193 "continue": true,

194 "stopReason": "optional",

195 "systemMessage": "optional",

196 "suppressOutput": false

197}

198```

199 

200| Field | Effect |

201| ---------------- | ----------------------------------------------- |

202| `continue` | If `false`, marks that hook run as stopped |

203| `stopReason` | Recorded as the reason for stopping |

204| `systemMessage` | Surfaced as a warning in the UI or event stream |

205| `suppressOutput` | Parsed today but not yet implemented |

206 

207Exit `0` with no output is treated as success and Codex continues.

208 

209`PreToolUse` and `PermissionRequest` support `systemMessage`, but `continue`,

210`stopReason`, and `suppressOutput` aren't currently supported for those events.

211 

212`PostToolUse` supports `systemMessage`, `continue: false`, and `stopReason`.

213`suppressOutput` is parsed but not currently supported for that event.

214 

215## Hooks

216 

217### SessionStart

218 

219`matcher` is applied to `source` for this event.

220 

221Fields in addition to [Common input fields](#common-input-fields):

222 

223| Field | Type | Meaning |

224| --- | --- | --- |

225| `source` | `string` | How the session started: `startup` or `resume` |

226 

227Plain text on `stdout` is added as extra developer context.

228 

229JSON on `stdout` supports [Common output fields](#common-output-fields) and this

230hook-specific shape:

231 

232```json

233{

234 "hookSpecificOutput": {

235 "hookEventName": "SessionStart",

236 "additionalContext": "Load the workspace conventions before editing."

237 }

238}

239```

240 

241That `additionalContext` text is added as extra developer context.

242 

243### PreToolUse

244 

245Work in progress

246 

247Currently `PreToolUse` only supports Bash tool interception. The model can

248still work around this by writing its own script to disk and then running that

249script with Bash, so treat this as a useful guardrail rather than a complete

250enforcement boundary

251 

252This doesn't intercept all shell calls yet, only the simple ones. The newer

253 `unified_exec` mechanism allows richer streaming stdin/stdout handling of

254shell, but interception is incomplete. Similarly, this doesn’t intercept MCP,

255Write, WebSearch, or other non-shell tool calls.

256 

257`matcher` is applied to `tool_name`, which currently always equals `Bash`.

258 

259Fields in addition to [Common input fields](#common-input-fields):

260 

261| Field | Type | Meaning |

262| --- | --- | --- |

263| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |

264| `tool_name` | `string` | Currently always `Bash` |

265| `tool_use_id` | `string` | Tool-call id for this invocation |

266| `tool_input.command` | `string` | Shell command Codex is about to run |

267 

268Plain text on `stdout` is ignored.

269 

270JSON on `stdout` can use `systemMessage` and can block a Bash command with this

271hook-specific shape:

272 

273```json

274{

275 "hookSpecificOutput": {

276 "hookEventName": "PreToolUse",

277 "permissionDecision": "deny",

278 "permissionDecisionReason": "Destructive command blocked by hook."

279 }

280}

281```

282 

283Codex also accepts this older block shape:

284 

285```json

286{

287 "decision": "block",

288 "reason": "Destructive command blocked by hook."

289}

290```

291 

292You can also use exit code `2` and write the blocking reason to `stderr`.

293 

294`permissionDecision: "allow"` and `"ask"`, legacy `decision: "approve"`,

295`updatedInput`, `additionalContext`, `continue: false`, `stopReason`, and

296`suppressOutput` are parsed but not supported yet, so they fail open.

297 

298### PermissionRequest

299 

300Work in progress

301 

302`PermissionRequest` runs when Codex is about to ask for approval, such as a

303shell escalation or managed-network approval. It can allow the request, deny

304the request, or decline to decide and let the normal approval prompt continue.

305It doesn't run for commands that don't need approval.

306 

307`matcher` is applied to `tool_name`, which currently always equals `Bash`.

308 

309Fields in addition to [Common input fields](#common-input-fields):

310 

311| Field | Type | Meaning |

312| --- | --- | --- |

313| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |

314| `tool_name` | `string` | Currently always `Bash` |

315| `tool_input.command` | `string` | Shell command associated with the approval request |

316| `tool_input.description` | `string | null` | Human-readable approval reason, when Codex has one |

317 

318Plain text on `stdout` is ignored.

319 

320To approve the request, return:

321 

322```json

323{

324 "hookSpecificOutput": {

325 "hookEventName": "PermissionRequest",

326 "decision": {

327 "behavior": "allow"

328 }

329 }

330}

331```

332 

333To deny the request, return:

334 

335```json

336{

337 "hookSpecificOutput": {

338 "hookEventName": "PermissionRequest",

339 "decision": {

340 "behavior": "deny",

341 "message": "Blocked by repository policy."

342 }

343 }

344}

345```

346 

347If multiple matching hooks return decisions, any `deny` wins. Otherwise, an

348`allow` lets the request proceed without surfacing the approval prompt. If no

349matching hook decides, Codex uses the normal approval flow.

350 

351Don't return `updatedInput`, `updatedPermissions`, or `interrupt` for

352`PermissionRequest`; those fields are reserved for future behavior and fail

353closed today.

354 

355### PostToolUse

356 

357Work in progress

358 

359Currently `PostToolUse` only supports Bash tool results. It’s not limited to

360commands that exit successfully: non-interactive `exec_command` calls can still

361trigger `PostToolUse` when Codex emits a Bash post-tool payload. It can’t undo

362side effects from the command that already ran.

363 

364This doesn't intercept all shell calls yet, only the simple ones. The newer

365 `unified_exec` mechanism allows richer streaming stdin/stdout handling of

366shell, but interception is incomplete. Similarly, this doesn’t intercept MCP,

367Write, WebSearch, or other non-shell tool calls.

368 

369`matcher` is applied to `tool_name`, which currently always equals `Bash`.

370 

371Fields in addition to [Common input fields](#common-input-fields):

372 

373| Field | Type | Meaning |

374| --- | --- | --- |

375| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |

376| `tool_name` | `string` | Currently always `Bash` |

377| `tool_use_id` | `string` | Tool-call id for this invocation |

378| `tool_input.command` | `string` | Shell command Codex just ran |

379| `tool_response` | `JSON value` | Bash tool output payload. Today this is usually a JSON string |

380 

381Plain text on `stdout` is ignored.

382 

383JSON on `stdout` can use `systemMessage` and this hook-specific shape:

384 

385```json

386{

387 "decision": "block",

388 "reason": "The Bash output needs review before continuing.",

389 "hookSpecificOutput": {

390 "hookEventName": "PostToolUse",

391 "additionalContext": "The command updated generated files."

392 }

393}

394```

395 

396That `additionalContext` text is added as extra developer context.

397 

398For this event, `decision: "block"` doesn't undo the completed Bash command.

399Instead, Codex records the feedback, replaces the tool result with that

400feedback, and continues the model from the hook-provided message.

401 

402You can also use exit code `2` and write the feedback reason to `stderr`.

403 

404To stop normal processing of the original tool result after the command has

405already run, return `continue: false`. Codex will replace the tool result with

406your feedback or stop text and continue from there.

407 

408`updatedMCPToolOutput` and `suppressOutput` are parsed but not supported yet,

409so they fail open.

410 

411### UserPromptSubmit

412 

413`matcher` isn't currently used for this event.

414 

415Fields in addition to [Common input fields](#common-input-fields):

416 

417| Field | Type | Meaning |

418| --- | --- | --- |

419| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |

420| `prompt` | `string` | User prompt that's about to be sent |

421 

422Plain text on `stdout` is added as extra developer context.

423 

424JSON on `stdout` supports [Common output fields](#common-output-fields) and

425this hook-specific shape:

426 

427```json

428{

429 "hookSpecificOutput": {

430 "hookEventName": "UserPromptSubmit",

431 "additionalContext": "Ask for a clearer reproduction before editing files."

432 }

433}

434```

435 

436That `additionalContext` text is added as extra developer context.

437 

438To block the prompt, return:

439 

440```json

441{

442 "decision": "block",

443 "reason": "Ask for confirmation before doing that."

444}

445```

446 

447You can also use exit code `2` and write the blocking reason to `stderr`.

448 

449### Stop

450 

451`matcher` isn't currently used for this event.

452 

453Fields in addition to [Common input fields](#common-input-fields):

454 

455| Field | Type | Meaning |

456| --- | --- | --- |

457| `turn_id` | `string` | Codex-specific extension. Active Codex turn id |

458| `stop_hook_active` | `boolean` | Whether this turn was already continued by `Stop` |

459| `last_assistant_message` | `string | null` | Latest assistant message text, if available |

460 

461`Stop` expects JSON on `stdout` when it exits `0`. Plain text output is invalid

462for this event.

463 

464JSON on `stdout` supports [Common output fields](#common-output-fields). To keep

465Codex going, return:

466 

467```json

468{

469 "decision": "block",

470 "reason": "Run one more pass over the failing tests."

471}

472```

473 

474You can also use exit code `2` and write the continuation reason to `stderr`.

475 

476For this event, `decision: "block"` doesn't reject the turn. Instead, it tells

477Codex to continue and automatically creates a new continuation prompt that acts

478as a new user prompt, using your `reason` as that prompt text.

479 

480If any matching `Stop` hook returns `continue: false`, that takes precedence

481over continuation decisions from other matching `Stop` hooks.

482 

483## Schemas

484 

485If you need the exact current wire format, see the generated schemas in the

486[Codex GitHub repository](https://github.com/openai/codex/tree/main/codex-rs/hooks/schema/generated).