SpyBara
Go Premium Account
2026
8 Mar 2026, 18:10
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, 12:28
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 +482 −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 

43## Config shape

44 

45Hooks are organized in three levels:

46 

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

48- A matcher group that decides when that event matches

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

50 

51```json

52{

53 "hooks": {

54 "SessionStart": [

55 {

56 "matcher": "startup|resume",

57 "hooks": [

58 {

59 "type": "command",

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

61 "statusMessage": "Loading session notes"

62 }

63 ]

64 }

65 ],

66 "PreToolUse": [

67 {

68 "matcher": "Bash",

69 "hooks": [

70 {

71 "type": "command",

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

73 "statusMessage": "Checking Bash command"

74 }

75 ]

76 }

77 ],

78 "PermissionRequest": [

79 {

80 "matcher": "Bash",

81 "hooks": [

82 {

83 "type": "command",

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

85 "statusMessage": "Checking approval request"

86 }

87 ]

88 }

89 ],

90 "PostToolUse": [

91 {

92 "matcher": "Bash",

93 "hooks": [

94 {

95 "type": "command",

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

97 "statusMessage": "Reviewing Bash output"

98 }

99 ]

100 }

101 ],

102 "UserPromptSubmit": [

103 {

104 "hooks": [

105 {

106 "type": "command",

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

108 }

109 ]

110 }

111 ],

112 "Stop": [

113 {

114 "hooks": [

115 {

116 "type": "command",

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

118 "timeout": 30

119 }

120 ]

121 }

122 ]

123 }

124}

125```

126 

127Notes:

128 

129- `timeout` is in seconds.

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

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

132- `statusMessage` is optional.

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

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

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

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

137 

138## Matcher patterns

139 

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

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

142event.

143 

144Only some current Codex events honor `matcher`:

145 

146| Event | What `matcher` filters | Notes |

147| --- | --- | --- |

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

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

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

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

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

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

154 

155Examples:

156 

157- `Bash`

158- `startup|resume`

159- `Edit|Write`

160 

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

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

163 

164## Common input fields

165 

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

167 

168These are the shared fields you will usually use:

169 

170| Field | Type | Meaning |

171| --- | --- | --- |

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

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

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

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

176| `model` | `string` | Active model slug |

177 

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

179 

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

181 

182## Common output fields

183 

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

185fields:

186 

187```json

188{

189 "continue": true,

190 "stopReason": "optional",

191 "systemMessage": "optional",

192 "suppressOutput": false

193}

194```

195 

196| Field | Effect |

197| ---------------- | ----------------------------------------------- |

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

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

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

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

202 

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

204 

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

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

207 

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

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

210 

211## Hooks

212 

213### SessionStart

214 

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

216 

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

218 

219| Field | Type | Meaning |

220| --- | --- | --- |

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

222 

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

224 

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

226hook-specific shape:

227 

228```json

229{

230 "hookSpecificOutput": {

231 "hookEventName": "SessionStart",

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

233 }

234}

235```

236 

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

238 

239### PreToolUse

240 

241Work in progress

242 

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

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

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

246enforcement boundary

247 

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

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

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

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

252 

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

254 

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

256 

257| Field | Type | Meaning |

258| --- | --- | --- |

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

260| `tool_name` | `string` | Currently always `Bash` |

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

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

263 

264Plain text on `stdout` is ignored.

265 

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

267hook-specific shape:

268 

269```json

270{

271 "hookSpecificOutput": {

272 "hookEventName": "PreToolUse",

273 "permissionDecision": "deny",

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

275 }

276}

277```

278 

279Codex also accepts this older block shape:

280 

281```json

282{

283 "decision": "block",

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

285}

286```

287 

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

289 

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

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

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

293 

294### PermissionRequest

295 

296Work in progress

297 

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

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

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

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

302 

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

304 

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

306 

307| Field | Type | Meaning |

308| --- | --- | --- |

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

310| `tool_name` | `string` | Currently always `Bash` |

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

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

313 

314Plain text on `stdout` is ignored.

315 

316To approve the request, return:

317 

318```json

319{

320 "hookSpecificOutput": {

321 "hookEventName": "PermissionRequest",

322 "decision": {

323 "behavior": "allow"

324 }

325 }

326}

327```

328 

329To deny the request, return:

330 

331```json

332{

333 "hookSpecificOutput": {

334 "hookEventName": "PermissionRequest",

335 "decision": {

336 "behavior": "deny",

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

338 }

339 }

340}

341```

342 

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

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

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

346 

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

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

349closed today.

350 

351### PostToolUse

352 

353Work in progress

354 

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

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

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

358side effects from the command that already ran.

359 

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

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

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

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

364 

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

366 

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

368 

369| Field | Type | Meaning |

370| --- | --- | --- |

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

372| `tool_name` | `string` | Currently always `Bash` |

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

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

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

376 

377Plain text on `stdout` is ignored.

378 

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

380 

381```json

382{

383 "decision": "block",

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

385 "hookSpecificOutput": {

386 "hookEventName": "PostToolUse",

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

388 }

389}

390```

391 

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

393 

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

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

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

397 

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

399 

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

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

402your feedback or stop text and continue from there.

403 

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

405so they fail open.

406 

407### UserPromptSubmit

408 

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

410 

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

412 

413| Field | Type | Meaning |

414| --- | --- | --- |

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

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

417 

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

419 

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

421this hook-specific shape:

422 

423```json

424{

425 "hookSpecificOutput": {

426 "hookEventName": "UserPromptSubmit",

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

428 }

429}

430```

431 

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

433 

434To block the prompt, return:

435 

436```json

437{

438 "decision": "block",

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

440}

441```

442 

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

444 

445### Stop

446 

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

448 

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

450 

451| Field | Type | Meaning |

452| --- | --- | --- |

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

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

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

456 

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

458for this event.

459 

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

461Codex going, return:

462 

463```json

464{

465 "decision": "block",

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

467}

468```

469 

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

471 

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

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

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

475 

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

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

478 

479## Schemas

480 

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

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