TL;DR
- Codex ships an “under development” Hooks feature that you can enable with
codex features enable codex_hooks. - It supports three events:
SessionStart,UserPromptSubmit, andStop.
Background
This article was verified against Codex v0.115.0 (March 2026). Since this is an in-development feature, behavior may change in future versions.
Codex doesn’t officially have a Hooks feature yet. The official docs say nothing about it, and the Changelog only mentions a one-liner: “experimental hooks engine.”
But it turns out a working Hooks implementation is already shipping under the “under development” status. Running codex features list revealed a codex_hooks feature flag, and once enabled, hooks actually fire.
The trouble is that the config JSON structure and the list of supported events are documented nowhere — so I read the Rust source in the OSS openai/codex repository (Apache License 2.0), specifically codex-rs/hooks/, to nail down the spec. This post summarizes what I found.
1. Discovery and enablement
Checking the feature flag
Codex has a codex features list command that prints the feature flag list. That’s how I learned about Hooks in the first place: I ran it and saw codex_hooks listed as “under development.”
$ codex features list
codex_hooks under development false
Disabled by default.
Enable it
# persisted to config.toml
codex features enable codex_hooks
Once enabled, you’ll see this warning at startup:
⚠ Under-development features enabled: codex_hooks. Under-development features are incomplete
and may behave unpredictably.
2. Configuration file
Path and format
| Item | Value |
|---|---|
| Global config | ~/.codex/hooks.json |
| Project config | <project>/.codex/hooks.json |
| Format | JSON |
Codex’s general config lives in ~/.codex/config.toml (TOML), but Hooks config is a separate hooks.json file in JSON. Writing hooks into config.toml won’t be picked up.
~/.codex/
├── config.toml ← general settings & feature flags
└── hooks.json ← hooks config (JSON)
JSON structure
The structure is a bit unusual: instead of putting handlers directly under each event name, you need an extra wrapper layer. In the source (codex-rs/hooks/src/engine/config.rs), it’s defined as a MatcherGroup struct with matcher (a regex filter) and hooks (an array of handlers).
hooks.<EventName>[].matcher → filter condition (optional)
hooks.<EventName>[].hooks[] → array of handlers to run
A concrete JSON looks like this:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo 'session started'",
"timeout": 10
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "echo 'session stopped'",
"timeout": 10
}
]
}
]
}
}
timeout is optional and defaults to 600 seconds.
What doesn’t work
If you don’t know about the nested structure and put handlers directly under the event array, nothing fires:
{
"hooks": {
"SessionStart": [
{
"type": "command",
"command": "echo 'session started'"
}
]
}
}
I actually tried this form first, hit silence, and only realized the nesting was required after reading the source. With no official docs, the source is the only accurate reference.
3. Supported events
The three supported events
| Event | When it fires |
|---|---|
SessionStart | When a session starts or resumes |
UserPromptSubmit | When the user submits a prompt |
Stop | When the agent finishes responding |
Tool execution and subagent-related events aren’t implemented yet.
Handler types
A “handler type” specifies what to actually run when a hook fires. The source (codex-rs/hooks/src/engine/config.rs) defines three types — command, prompt, and agent — but only command (run a shell command) actually works today.
| Type | Behavior | Status |
|---|---|---|
command | Runs a shell command | Works |
prompt | Injects a prompt into the agent’s context | Not implemented (logs "skipping prompt hook") |
agent | Spawns another agent to handle the work | Not implemented |
async: true (asynchronous execution) is also defined but not implemented. Once prompt lands, you could “inject extra instructions on every prompt submit”; with agent, you could “kick off a review agent after each response.” For now, though, the only option is “event fires → shell command runs.”
JSON payload passed to stdin
Hook commands run via $SHELL -l -c "<command>", and a JSON payload is piped to stdin.
{
"session_id": "019d07b0-...",
"cwd": "/Users/user/project",
"hook_event_name": "SessionStart",
"model": "o3-pro",
"permission_mode": "default",
"source": "startup",
"transcript_path": null
}
| Field | Description |
|---|---|
session_id | Session ID (UUID v7) |
cwd | Working directory |
hook_event_name | Event name |
model | Model in use |
permission_mode | Approval policy |
source | Start type (SessionStart only) |
transcript_path | Path to the conversation history file (currently null) |
Some fields are added per-event:
| Field | Event | Description |
|---|---|---|
source | SessionStart | startup / resume / clear |
prompt | UserPromptSubmit | The user’s input text |
turn_id | UserPromptSubmit / Stop | Conversation turn identifier |
last_assistant_message | Stop | The last assistant response |
stop_hook_active | Stop | Whether a Stop hook is active |
session_id, cwd, hook_event_name, model, and permission_mode are common to all events.
4. Use case: unified management of multiple agent sessions
I built a desktop app that uses AI coding agent Hooks to centrally track the state of multiple sessions. It listens for SessionStart / Stop events and updates a session list view.
Now that Codex supports Hooks too, I extended the same app to manage Codex sessions alongside the others.
What I configure in hooks.json
When you write events and commands into hooks.json, Codex runs the commands when those events fire. Conversely, if hooks.json is empty or missing, nothing happens — even with the feature flag on.
In the example below, every event does the same thing: read the JSON payload from stdin via $(cat) and forward it as-is to my app’s API with curl.
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "curl -s -X POST http://localhost:3000/api/hook -H 'Content-Type: application/json' -d \"$(cat)\"",
"timeout": 10
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "curl -s -X POST http://localhost:3000/api/hook -H 'Content-Type: application/json' -d \"$(cat)\"",
"timeout": 10
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "curl -s -X POST http://localhost:3000/api/hook -H 'Content-Type: application/json' -d \"$(cat)\"",
"timeout": 10
}
]
}
]
}
}
The receiving app inspects session_id and hook_event_name in the JSON to update session state. Because this pattern is shared across other tools’ Hooks, a single API endpoint can centralize multiple agent tools together.
5. Limitations and outlook
Current limitations
- Under-development status: The API may change.
- Coarse event granularity: There’s no per-tool-execution hook, so you can’t track real-time progress mid-task.
- command type only:
prompt,agent, andasynchandler types aren’t supported. - No official docs: You have to read the source to learn the actual spec.
The OSS upside
For all those limitations, Codex is OSS under Apache License 2.0, which means you can verify the Hooks implementation directly against the source. Everything in this post came from reading codex-rs/hooks/src/. Being able to fall back to the source when the spec is unclear is a real comfort.
What’s next
The Discussion #2150 thread on GitHub has lots of community comments requesting Hooks features, which signals strong interest. I’d expect tool events and additional handler types to land over time.
Summary
Codex Hooks is still in development with limited functionality, but flipping the feature flag gets you working hooks today. For use cases like detecting session start/end and running commands, it’s already useful enough.
References
- Codex Changelog - announcement of the hooks engine
- openai/codex - GitHub - source code (Apache License 2.0)
- Discussion #2150: Hook would be a great feature - community request thread