Summary
Project hooks for tool.before and tool.after events never fire when shell/container.exec commands are executed. The hook infrastructure exists and works (proven by session.start hooks firing correctly), but shell tool execution bypasses it entirely.
Environment
- Version: v0.5.15
- Platform: macOS Darwin 25.1.0
- Config location:
~/.code/config.toml
Steps to Reproduce
- Configure a
tool.after hook in ~/.code/config.toml:
[[projects."/path/to/project".hooks]]
name = "test-tool-after"
event = "tool.after"
run = ["/path/to/log-hook.sh"]
timeout_ms = 5000
- Create a simple logging hook script:
#!/bin/bash
echo "$(date) | EVENT: $CODE_HOOK_EVENT | NAME: $CODE_HOOK_NAME" >> /tmp/hooks.log
- Start a coder session and execute any shell command
- Check
/tmp/hooks.log - no tool.after entries appear
Expected Behavior
The tool.after hook should fire after every shell command execution, similar to how session.start fires at session initialization.
Actual Behavior
session.start hooks fire correctly ✅
tool.before hooks never fire ❌
tool.after hooks never fire ❌
Root Cause Analysis
In code-rs/core/src/codex.rs:
The Bug: handle_container_exec_with_params (line ~9677) directly calls process_exec_tool_call (line ~10562) WITHOUT invoking run_hooks_for_exec_event.
Call path that bypasses hooks:
handle_function_call (line ~6573)
└─> for "shell"/"container.exec" calls...
└─> handle_container_exec_with_params (line ~6593)
└─> process_exec_tool_call (line ~10562) ← DIRECTLY CALLED, NO HOOKS
Correct hook-aware path (exists but unused for shell tools):
run_exec_with_events (line ~2390)
└─> run_exec_with_events_inner (line ~2498)
└─> run_hooks_for_exec_event(ToolBefore) (line ~2550)
└─> process_exec_tool_call (line ~2565)
└─> run_hooks_for_exec_event(ToolAfter) (line ~2607)
The run_exec_with_events_inner function correctly wraps command execution with hook calls, but handle_container_exec_with_params bypasses this entirely.
Evidence
Session JSONL logs confirm formal function calls ARE being made:
{"type":"function_call","name":"shell","call_id":"call_xxx","arguments":"{\"command\":[\"sh\",\"-lc\",\"echo test\"]}"}
{"type":"function_call_output","call_id":"call_xxx","output":"{\"output\":\"test\\n\",\"metadata\":{\"exit_code\":0}}"}
But hook logs only show session.start events - never tool.before or tool.after.
Suggested Fix
Option A: Have handle_container_exec_with_params call run_exec_with_events_inner instead of process_exec_tool_call directly.
Option B: Add explicit run_hooks_for_exec_event calls in handle_container_exec_with_params before and after the process_exec_tool_call invocation.
Impact
This affects all observation-only hooks that users configure for tool execution monitoring, logging, or integration purposes. The file.before_write and file.after_write hooks may also be affected (same code path for apply_patch).
Summary
Project hooks for
tool.beforeandtool.afterevents never fire when shell/container.exec commands are executed. The hook infrastructure exists and works (proven bysession.starthooks firing correctly), but shell tool execution bypasses it entirely.Environment
~/.code/config.tomlSteps to Reproduce
tool.afterhook in~/.code/config.toml:/tmp/hooks.log- notool.afterentries appearExpected Behavior
The
tool.afterhook should fire after every shell command execution, similar to howsession.startfires at session initialization.Actual Behavior
session.starthooks fire correctly ✅tool.beforehooks never fire ❌tool.afterhooks never fire ❌Root Cause Analysis
In
code-rs/core/src/codex.rs:The Bug:
handle_container_exec_with_params(line ~9677) directly callsprocess_exec_tool_call(line ~10562) WITHOUT invokingrun_hooks_for_exec_event.Call path that bypasses hooks:
Correct hook-aware path (exists but unused for shell tools):
The
run_exec_with_events_innerfunction correctly wraps command execution with hook calls, buthandle_container_exec_with_paramsbypasses this entirely.Evidence
Session JSONL logs confirm formal function calls ARE being made:
{"type":"function_call","name":"shell","call_id":"call_xxx","arguments":"{\"command\":[\"sh\",\"-lc\",\"echo test\"]}"} {"type":"function_call_output","call_id":"call_xxx","output":"{\"output\":\"test\\n\",\"metadata\":{\"exit_code\":0}}"}But hook logs only show
session.startevents - nevertool.beforeortool.after.Suggested Fix
Option A: Have
handle_container_exec_with_paramscallrun_exec_with_events_innerinstead ofprocess_exec_tool_calldirectly.Option B: Add explicit
run_hooks_for_exec_eventcalls inhandle_container_exec_with_paramsbefore and after theprocess_exec_tool_callinvocation.Impact
This affects all observation-only hooks that users configure for tool execution monitoring, logging, or integration purposes. The
file.before_writeandfile.after_writehooks may also be affected (same code path forapply_patch).