## Why
Writing hook input before draining output can deadlock when pipe buffers fill. Stdin writes also ran outside the hook timeout, allowing hooks that never read input to hang indefinitely.
## What changed
Write stdin concurrently with draining stdout and stderr, and apply the configured timeout to both input delivery and command completion. Preserve handling of broken pipes and distinct stdin and wait errors.
## Testing
Add a regression test that sends a large input to a hook that fills stdout and stderr without reading stdin. Verify that output drains and the hook reports its configured two-second timeout.
GitOrigin-RevId: 48cf33a2acfebbc5122a4522660d7a6b3fb1e9b0
## Why
Shell startup code that accesses the controlling terminal can stop a hook on background terminal I/O, preventing the hook command from running.
## What changed
Use `detach_from_tty` before executing Unix hook commands to avoid inheriting the controlling terminal while preserving process-group cleanup.
## Testing
Add a Unix regression test that runs under a controlling terminal and uses `BASH_ENV` to invoke `stty` during shell startup. Verify that the hook completes successfully and produces the expected output.
GitOrigin-RevId: 231125d4b169fb1bd828e3f8e96476a13bd25d26
## What changed
- Discover inline hooks from executor-provided plugin manifests and accept only
the bundled Computer Use `Stop` hook for `node_repl.turn_ended`.
- Add the accepted hook to the current step's hook engine with
executor-scoped provenance.
- Run executor-scoped hooks in the background after regular stop-hook
decisions, without delaying turn completion, applying control effects, or
exposing them in hook summaries. Skip them when regular hooks only block
continuation.
## Testing
- Added coverage for manifest filtering and option preservation.
- Added coverage for background execution, interaction with regular stop
hooks, and non-blocking turn completion.
GitOrigin-RevId: c0f75f83a23ba8a83bb3a5d5997c4882aacc32d6
## What changed
- Capture the process environment when the hook registry is created and reuse
that snapshot across configuration reloads.
- Clear the live environment before launching command hooks and legacy notify
commands, then apply hook-specific overrides and scrub non-inheritable
credentials.
- Resolve the default shell from the captured environment.
## Testing
Add coverage for snapshot replay, overrides, credential scrubbing, default
shell selection, non-Unicode values, and runtime reconfiguration.
GitOrigin-RevId: fee60c88e842980cdfc1bd49b14b62b9a56b08cd
## What changed
- Discover synchronous `mcp_tool` hook handlers and invoke their configured MCP server and tool through a supplied executor.
- Expand nested hook-event placeholders in MCP tool inputs while preserving JSON types, and process tool output through the existing hook output contract.
- Represent hook details as handler-specific metadata in `hooks/list`, including MCP server and tool fields, and show those details in the TUI hooks browser.
- Skip unsupported `SessionEnd` MCP hooks and runtimes without MCP invocation support with startup warnings.
## Testing
- Cover argument expansion, missing placeholders, MCP invocation and hook decisions, discovery warnings, `hooks/list` metadata, and TUI rendering.
GitOrigin-RevId: 295b845471fe92bd7ad7cd272fbcd2c3713912e0
## What changed
- Represent configured handlers by handler kind and route execution through the
hooks engine, while preserving command hook behavior.
- Reject MCP tool inputs containing values such as `null` that cannot be
represented in TOML for trust hashing.
## Testing
- Add coverage for rejecting `null` at the top level and within nested MCP tool
input objects and arrays.
GitOrigin-RevId: 3854a349c2a721691450467592f99cf57e6260ef
## Why
Model-reachable child processes should not inherit Codex launch context.
## What changed
- Treat `OPENAI_FEDERATION_RULE_ID` and `OPENAI_IDENTITY_TOKEN_FILE` as non-inheritable environment variables, with case-insensitive matching.
- Remove them after shell environment policy overrides and before spawning commands across execution, MCP, hooks, Git helpers, and remote helper processes.
## Testing
- Cover inherited and explicitly configured variants, including mixed-case names.
- Verify the variables are absent from real child environments and app-server command and process execution.
GitOrigin-RevId: 2535527893985fef0995617f4c5b2462bea7c136
## Why
Hook configurations can mark command handlers as asynchronous, but Codex previously skipped those handlers outside `SessionEnd`.
## What changed
- Run asynchronous command hooks in the background with a per-session concurrency limit, while keeping `SessionEnd` hooks synchronous.
- Prevent asynchronous hooks from blocking, stopping, rewriting, or otherwise controlling the operation that launched them.
- Deliver warnings and additional context at safe turn boundaries: inject results into an active turn after sampling, or buffer them ahead of the next user prompt when the session is idle.
- Preserve in-flight hooks across configuration reloads, scope spilled output to the thread, and abort outstanding work during session shutdown.
## Testing
Add unit and integration coverage for background scheduling, concurrency, output parsing, active and idle result delivery, configuration reloads, and shutdown cleanup.
GitOrigin-RevId: 8094552e4afe7b47b09a61bb575bb20f4e491d8d
## Why
Timing out a command hook can leave descendant processes running after the hook itself is stopped.
## What changed
- Run hook commands in a process group on Unix and a job object on Windows.
- Terminate the full process tree when hook execution is cancelled or fails, with `taskkill` as a Windows fallback.
- Preserve descendants when a hook completes successfully so intentionally detached helpers can continue running.
GitOrigin-RevId: 1a18653d34c6f1685ef8c4f8e8546663b276292a
## Why
A hook can exit successfully without consuming its JSON input. If it closes
stdin while Codex is still writing, the resulting broken pipe should not hide
the hook's exit status and output.
## What changed
Ignore broken-pipe errors when writing hook stdin and continue waiting for the
process result. Other stdin write errors remain failures.
## Testing
Add a cross-platform regression test that sends a large input to a
fast-exiting hook and verifies that its successful status and stdout are
preserved.
GitOrigin-RevId: e1b6e7c6ac5e39232e49b45245ea0df9d34d1fe6
## Why
Windows hook commands whose executable paths contain spaces fail when their
outer quotes are escaped during argument construction.
## What changed
- Pass hook command strings as raw arguments to the default Windows shell.
- Do the same for configured shells that use the case-insensitive `/c` flag,
while retaining normal argument handling for other shells.
## Testing
Add a Windows regression test that runs a quoted `.cmd` hook from a directory
with spaces through both the default shell and an explicit `COMSPEC /c` shell.
GitOrigin-RevId: 0e7ce44203e01d3823daaaec750c2aeadfcd34b7
Adds a single trace span around configured hook command execution so
slow installed hooks are visible directly in traces.
The span records low-cardinality hook metadata and command outcome
without command text, hook input, stdout, or stderr.
## Why
Plugins can bundle lifecycle hooks, but Codex previously only discovered
hooks from user, project, and managed config layers. This adds the
plugin discovery and runtime plumbing needed for plugin-bundled hooks
while keeping execution behind the `plugin_hooks` feature flag.
## What
- Discovers plugin hook sources from each plugin's default
`hooks/hooks.json`.
- Supports `plugin.json` manifest `hooks` entries as either relative
paths or inline hook objects.
- Plumbs discovered plugin hook sources through plugin loading into the
hook runtime when `plugin_hooks` is enabled.
- Marks plugin-originated hook runs as `HookSource::Plugin`.
- Injects `PLUGIN_ROOT` and `CLAUDE_PLUGIN_ROOT` into plugin hook
command environments.
- Updates generated schemas and hook source metadata for the plugin hook
source.
## Stack
1. This PR - openai/codex#19705
2. openai/codex#19778
3. openai/codex#19840
4. openai/codex#19882
## Reviewer Notes
- Core logic is in `codex-rs/core-plugins/src/loader.rs` and
`codex-rs/hooks/src/engine/discovery.rs`
- Moved existing / adding new tests to
`codex-rs/core-plugins/src/loader_tests.rs` hence the large diff there
- Otherwise mostly plumbing and minor schema updates
### Core Changes
The `codex-rs/core` changes are limited to wiring plugin hook support
into existing core flows:
- `core/src/session/session.rs` conditionally pulls effective plugin
hook sources and plugin hook load warnings from `PluginsManager` when
`plugin_hooks` is enabled, then passes them into `HooksConfig`.
- `core/src/hook_runtime.rs` adds the `plugin` metric tag for
`HookSource::Plugin`.
- `core/config.schema.json` picks up the new `plugin_hooks` feature
flag, and `core/src/plugins/manager_tests.rs` updates fixtures for the
added plugin hook fields.
---------
Co-authored-by: Codex <noreply@openai.com>
(Experimental)
This PR adds a first MVP for hooks, with SessionStart and Stop
The core design is:
- hooks live in a dedicated engine under codex-rs/hooks
- each hook type has its own event-specific file
- hook execution is synchronous and blocks normal turn progression while
running
- matching hooks run in parallel, then their results are aggregated into
a normalized HookRunSummary
On the AppServer side, hooks are exposed as operational metadata rather
than transcript-native items:
- new live notifications: hook/started, hook/completed
- persisted/replayed hook results live on Turn.hookRuns
- we intentionally did not add hook-specific ThreadItem variants
Hooks messages are not persisted, they remain ephemeral. The context
changes they add are (they get appended to the user's prompt)