Regression: apply_patch custom tool calls do not fire PostToolUse hooks in 0.136.0
Summary
apply_patch does not appear to emit PostToolUse hooks in a Codex CLI 0.136.0 session, despite project hooks being enabled/trusted and despite apply_patch hook support being documented and previously reported as fixed in #16732 / #17794.
The observable symptom is stronger than "additionalContext did not render": a hook wrapper that writes a debug log at process start is never invoked after real apply_patch calls.
Related issues:
- #16732: ApplyPatchHandler did not emit hooks; reported fixed in 0.123.0.
- #17794: file write operations /
functions.apply_patchdid not fire hooks. - #20204: broader hook coverage gaps across handlers.
This issue is for a current regression or uncovered surface in 0.136.0 where model-visible custom_tool_call entries named apply_patch do not run the configured project PostToolUse hook.
Environment
- Codex CLI:
codex-cli 0.136.0 - Install method: npm package
@openai/codex - Platform: Windows 11 /
windows-x86_64 - Source: interactive CLI / TUI session (
originator: codex-tui,source: cliin rollout metadata) - Repo trust: project is trusted via user config:
[projects.'c:\users\trots\openlsp'] trust_level = "trusted"- Hooks feature: enabled:
[features] hooks = true- Hook trust: project hook has a trusted hash in
[hooks.state]
Hook configuration
Project .codex/config.toml:
[[hooks.PostToolUse]]
matcher = "Edit|Write|apply_patch|mcp__.*"
[[hooks.PostToolUse.hooks]]
type = "command"
command = "sh \"$(git rev-parse --show-toplevel)/.codex/hooks/openlsp-codex-hook.sh\""
command_windows = "powershell -NoProfile -ExecutionPolicy Bypass -Command \"& { $root = (git rev-parse --show-toplevel).Trim(); & (Join-Path $root '.codex/hooks/openlsp-codex-hook.ps1') }\""
timeout = 30
statusMessage = "Checking OpenLSP diagnostics"
The Windows wrapper starts by appending to .codex/hook-debug.log, before running any project-specific logic. That makes it easy to distinguish these cases:
- Hook runner invoked wrapper, but wrapper produced no findings.
- Hook runner never invoked wrapper.
Reproduction
- Configure and trust the
PostToolUsehook above. - Ensure the wrapper writes to
.codex/hook-debug.logimmediately on startup. - Clear
.codex/hook-debug.log. - Ask Codex to create a syntactically invalid TypeScript file, which causes a real
apply_patchcall. Example observed tool entry from the rollout file:
{
"type": "custom_tool_call",
"name": "apply_patch",
"input": "*** Begin Patch\n*** Add File: src/openlsp-hook-final-smoke.ts\n+export const openLspHookFinal =\n*** End Patch\n"
}
- Check
.codex/hook-debug.log.
Actual behavior
apply_patchsucceeds and modifies the file..codex/hook-debug.logremains empty.- No hook
additionalContextis returned to the agent.
This indicates the configured hook wrapper was not invoked at all for the apply_patch tool call.
Expected behavior
PostToolUse should fire for the apply_patch tool call and invoke the configured wrapper. At minimum, .codex/hook-debug.log should contain a start/end entry from the wrapper.
If the hook prints valid JSON like this:
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "...diagnostics..."
}
}
then that additionalContext should become available to the agent according to the hook contract.
Control checks
These controls all passed in the same repo/session environment:
- Manually invoking the wrapper with a synthetic
PostToolUsestdin event writes.codex/hook-debug.logand exits 0. - Manually invoking the wrapper with the same invalid file returns valid
hookSpecificOutput.additionalContextJSON. - Running the diagnostics command directly on the invalid file reports the expected syntax error.
- The project hook is trusted and hooks are enabled according to user config /
codex features list.
Example manual wrapper output:
{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"OpenLSP found issues in 1 check(s):\n\n[diagnostics] openlsp-hook-final-smoke.ts (ok)\n\nERROR [1:1] Syntax error near 'export const openLspHookFinal ='"}}
Because the debug log remains empty only after the real apply_patch, this looks like the hook runner is not being called for this custom_tool_call apply_patch path in 0.136.0.
Notes
The hook consumer initially only parsed patch text under tool_input.command; that has been fixed locally to also parse raw string tool_input / patch text. The failure described here is independent of that parser issue: the wrapper's first debug-log write does not run at all after real apply_patch calls.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗