[hooks] add non-streaming (non-stdin style) shell-only PreToolUse support (#15211)

- add `PreToolUse` hook for bash-like tool execution only at first
- block shell execution before dispatch with deny-only hook behavior
- introduces common.rs matcher framework for matching when hooks are run

example run:

```
› run three parallel echo commands, and the second one should echo "[block-pre-tool-use]" as a test


• Running the three echo commands in parallel now and I’ll report the output directly.

• Running PreToolUse hook: name for demo pre tool use hook

• Running PreToolUse hook: name for demo pre tool use hook

• Running PreToolUse hook: name for demo pre tool use hook

PreToolUse hook (completed)
  warning: wizard-tower PreToolUse demo inspected Bash: echo "first parallel echo"
  
PreToolUse hook (blocked)
  warning: wizard-tower PreToolUse demo blocked a Bash command on purpose.
  feedback: PreToolUse demo blocked the command. Remove [block-pre-tool-use] to continue.

PreToolUse hook (completed)
  warning: wizard-tower PreToolUse demo inspected Bash: echo "third parallel echo"

• Ran echo "first parallel echo"
  └ first parallel echo

• Ran echo "third parallel echo"
  └ third parallel echo

• Three little waves went out in parallel.

  1. printed first parallel echo
  2. was blocked before execution because it contained the exact test string [block-pre-tool-use]
  3. printed third parallel echo

  There was also an unrelated macOS defaults warning around the successful commands, but the echoes
  themselves worked fine. If you want, I can rerun the second one with a slightly modified string so
  it passes cleanly.
```
This commit is contained in:
Andrei Eternal
2026-03-23 14:32:59 -07:00
committed by GitHub
parent 18f1a08bc9
commit 73bbb07ba8
38 changed files with 1877 additions and 55 deletions
+11
View File
@@ -10,6 +10,8 @@ use std::path::PathBuf;
use codex_config::ConfigLayerStack;
use codex_protocol::protocol::HookRunSummary;
use crate::events::pre_tool_use::PreToolUseOutcome;
use crate::events::pre_tool_use::PreToolUseRequest;
use crate::events::session_start::SessionStartOutcome;
use crate::events::session_start::SessionStartRequest;
use crate::events::stop::StopOutcome;
@@ -46,6 +48,7 @@ impl ConfiguredHandler {
fn event_name_label(&self) -> &'static str {
match self.event_name {
codex_protocol::protocol::HookEventName::PreToolUse => "pre-tool-use",
codex_protocol::protocol::HookEventName::SessionStart => "session-start",
codex_protocol::protocol::HookEventName::UserPromptSubmit => "user-prompt-submit",
codex_protocol::protocol::HookEventName::Stop => "stop",
@@ -105,6 +108,10 @@ impl ClaudeHooksEngine {
crate::events::session_start::preview(&self.handlers, request)
}
pub(crate) fn preview_pre_tool_use(&self, request: &PreToolUseRequest) -> Vec<HookRunSummary> {
crate::events::pre_tool_use::preview(&self.handlers, request)
}
pub(crate) async fn run_session_start(
&self,
request: SessionStartRequest,
@@ -113,6 +120,10 @@ impl ClaudeHooksEngine {
crate::events::session_start::run(&self.handlers, &self.shell, request, turn_id).await
}
pub(crate) async fn run_pre_tool_use(&self, request: PreToolUseRequest) -> PreToolUseOutcome {
crate::events::pre_tool_use::run(&self.handlers, &self.shell, request).await
}
pub(crate) fn preview_user_prompt_submit(
&self,
request: &UserPromptSubmitRequest,