mirror of
https://github.com/openai/codex.git
synced 2026-11-02 16:20:35 +00:00
[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:
1 parent
18f1a08bc9
commit
73bbb07ba8
38 files changed
+1877
-55
No files matched your search
@@ -14,6 +14,7 @@ use super::CommandShell;
|
||||
use super::ConfiguredHandler;
|
||||
use super::command_runner::CommandRunResult;
|
||||
use super::command_runner::run_command;
|
||||
use crate::events::common::matches_matcher;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ParsedHandler<T> {
|
||||
@@ -30,13 +31,9 @@ pub(crate) fn select_handlers(
|
||||
.iter()
|
||||
.filter(|handler| handler.event_name == event_name)
|
||||
.filter(|handler| match event_name {
|
||||
HookEventName::SessionStart => match (&handler.matcher, matcher_input) {
|
||||
(Some(matcher), Some(input)) => regex::Regex::new(matcher)
|
||||
.map(|regex| regex.is_match(input))
|
||||
.unwrap_or(false),
|
||||
(None, _) => true,
|
||||
_ => false,
|
||||
},
|
||||
HookEventName::PreToolUse | HookEventName::SessionStart => {
|
||||
matches_matcher(handler.matcher.as_deref(), matcher_input)
|
||||
}
|
||||
HookEventName::UserPromptSubmit | HookEventName::Stop => true,
|
||||
})
|
||||
.cloned()
|
||||
@@ -109,7 +106,9 @@ pub(crate) fn completed_summary(
|
||||
fn scope_for_event(event_name: HookEventName) -> HookScope {
|
||||
match event_name {
|
||||
HookEventName::SessionStart => HookScope::Thread,
|
||||
HookEventName::UserPromptSubmit | HookEventName::Stop => HookScope::Turn,
|
||||
HookEventName::PreToolUse | HookEventName::UserPromptSubmit | HookEventName::Stop => {
|
||||
HookScope::Turn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +171,50 @@ mod tests {
|
||||
assert_eq!(selected[1].display_order, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_tool_use_matches_tool_name() {
|
||||
let handlers = vec![
|
||||
make_handler(HookEventName::PreToolUse, Some("^Bash$"), "echo same", 0),
|
||||
make_handler(HookEventName::PreToolUse, Some("^Edit$"), "echo same", 1),
|
||||
];
|
||||
|
||||
let selected = select_handlers(&handlers, HookEventName::PreToolUse, Some("Bash"));
|
||||
|
||||
assert_eq!(selected.len(), 1);
|
||||
assert_eq!(selected[0].display_order, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_tool_use_star_matcher_matches_all_tools() {
|
||||
let handlers = vec![
|
||||
make_handler(HookEventName::PreToolUse, Some("*"), "echo same", 0),
|
||||
make_handler(HookEventName::PreToolUse, Some("^Edit$"), "echo same", 1),
|
||||
];
|
||||
|
||||
let selected = select_handlers(&handlers, HookEventName::PreToolUse, Some("Bash"));
|
||||
|
||||
assert_eq!(selected.len(), 1);
|
||||
assert_eq!(selected[0].display_order, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_tool_use_regex_alternation_matches_each_tool_name() {
|
||||
let handlers = vec![make_handler(
|
||||
HookEventName::PreToolUse,
|
||||
Some("Edit|Write"),
|
||||
"echo same",
|
||||
0,
|
||||
)];
|
||||
|
||||
let selected_edit = select_handlers(&handlers, HookEventName::PreToolUse, Some("Edit"));
|
||||
let selected_write = select_handlers(&handlers, HookEventName::PreToolUse, Some("Write"));
|
||||
let selected_bash = select_handlers(&handlers, HookEventName::PreToolUse, Some("Bash"));
|
||||
|
||||
assert_eq!(selected_edit.len(), 1);
|
||||
assert_eq!(selected_write.len(), 1);
|
||||
assert_eq!(selected_bash.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_prompt_submit_ignores_matcher() {
|
||||
let handlers = vec![
|
||||
|
||||
Reference in new issue
Block a user