Skip to content

Bug: tool.before/tool.after hooks never fire for shell commands #453

@paulbettner

Description

@paulbettner

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

  1. 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
  1. Create a simple logging hook script:
#!/bin/bash
echo "$(date) | EVENT: $CODE_HOOK_EVENT | NAME: $CODE_HOOK_NAME" >> /tmp/hooks.log
  1. Start a coder session and execute any shell command
  2. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions