Code Mode / functions.exec does not fire PostToolUse hooks for nested shell results

Open 💬 0 comments Opened Aug 16, 2026 by tohuw
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.json with [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 exec not firing PreToolUse; its proposed fix explicitly leaves PostToolUse out of scope. This report is the symmetric result-side gap.
  • #31015 covers replacement/redaction semantics after PostToolUse fires. 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:

  1. Register a trusted PostToolUse hook for Code Mode exec and nested shell calls.
  2. Execute a nested tool returning a known marker.
  3. Assert the hook receives the completed payload.
  4. Return a valid replacement/block result.
  5. 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.

View original on GitHub ↗