Code Mode / functions.exec does not fire PostToolUse hooks for nested shell results
Authorship disclosure: This issue was investigated and written by OpenAI Codex on behalf of @tohuw, who reviewed and requested its publication.
Important bit
A configured, visible, explicitly approved, and trusted PostToolUse hook does not fire for shell execution nested under Code Mode / functions.exec in Codex Desktop on Windows. The failure is silent: a deliberately compressible 7,634-token tool result was delivered unchanged, while invoking the same hook directly reduced an equivalent 30,191-character payload to 245 characters and returned a valid decision: "block" response.
This leaves integrations unable to observe or replace results from the execution path Codex actually selected, even though /hooks shows the hook and Codex records its trusted hash.
Environment
- Windows
- Codex Desktop app build
26.803.81509 - Bundled Codex CLI
0.147.0 - Global
hooks.jsonwith[features].hooks = true - Hook approved through
/hooks
Reproduction
Configure a PostToolUse command hook in ~/.codex/hooks.json. During diagnosis, the matcher was broadened to cover every observed/likely shell name:
{
"hooks": {
"PostToolUse": [
{
"matcher": "^(Bash|shell_command|exec_command|functions\\.exec)$",
"hooks": [
{
"type": "command",
"command": "pwsh.exe -NoProfile -File C:/Users/USER/.local/hooks/compress-result.ps1"
}
]
}
]
}
}
Restart Codex, run /hooks, and approve the hook. Confirm trust is persisted:
[hooks.state.'C:\Users\USER\.codex\hooks.json:post_tool_use:0:0']
trusted_hash = "sha256:..."
Then ask Codex to run this through its shell tool:
(1..300) | ForEach-Object {
@{id=$_; status='ok'; message='completed successfully'; duration_ms=12; repeated='same-value'} |
ConvertTo-Json -Compress
}
Actual behavior
The live tool result returned all 303 lines and was truncated by the client only after reaching 7,634 tokens:
Warning: truncated output (original token count: 7634)
Total output lines: 303
No replacement summary appeared and there was no hook failure diagnostic.
The test was repeated after each matcher/configuration change, with a full Codex restart and renewed /hooks approval. The persisted trusted hash and parsed hook configuration were verified after restart.
Control test
The hook executable and its downstream compressor were exercised directly with an equivalent PostToolUse event envelope:
HOOK_RESULT=block
original_chars=30191
compressed_chars=245
The returned JSON used the currently accepted blocking shape:
{
"decision": "block",
"reason": "<compressed result>",
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Tool output compressed before the model read it."
}
}
This isolates the failure to Codex event dispatch/coverage rather than hook trust, JSON validity, process launch, or the compressor.
Expected behavior
Code Mode execution and its nested tool calls should emit symmetric PreToolUse and PostToolUse events. A trusted PostToolUse hook matching the execution surface should receive the completed result and have its documented decision applied.
At minimum, Codex should report that this execution path does not support PostToolUse; silently accepting and trusting a hook that can never fire makes integrations appear active when they are not.
Related issues
- #23411 covers Code Mode
execnot firingPreToolUse; its proposed fix explicitly leavesPostToolUseout of scope. This report is the symmetric result-side gap. - #31015 covers replacement/redaction semantics after
PostToolUsefires. Here, the hook never fires at all. - #20204 tracks inconsistent hook coverage across other handlers.
- Downstream integration report with the original discovery and measurements: https://github.com/3rg0n/thlibo/issues/126
Musings / possible fix direction
The behavior is consistent with Code Mode's outer exec handler and/or nested dispatch path lacking a PostToolUse payload implementation, analogous to the missing PreToolUse implementation identified in #23411.
Potentially useful acceptance coverage:
- Register a trusted
PostToolUsehook for Code Modeexecand nested shell calls. - Execute a nested tool returning a known marker.
- Assert the hook receives the completed payload.
- Return a valid replacement/block result.
- Assert the model-facing tool result uses the hook response rather than the original output.
I am available to test a diagnostic or patched Windows build against the downstream thlibo integration.