mirror of
https://github.com/openai/codex.git
synced 2026-09-17 12:23:33 +00:00
CHAINED PR - note that base is eternal/hooks-pretooluse-bash, not main -- so the following PR should be first Matching post-tool hook to the pre-tool functionality here: https://github.com/openai/codex/pull/15211 So, PreToolUse calls for plain shell calls, allows blocking. This PostToolUse call runs after the command executed example run: ``` › as a test, run in parallel the following commands: - echo 'one' - echo '[block-pre-tool-use]' - echo '[block-post-tool-use]' ⚠ MCP startup incomplete (failed: notion, linear) • Cruising through those three commands in parallel now, and I’ll share the exact outputs right after they land. • Running PreToolUse hook: checking the observatory runes • Running PreToolUse hook: checking the observatory runes • Running PreToolUse hook: checking the observatory runes 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 'one' PreToolUse hook (completed) warning: wizard-tower PreToolUse demo inspected Bash: echo '[block-post-tool-use]' • Ran echo '[block-post-tool-use]' └ [block-post-tool-use] • Ran echo 'one' └ one • Running PostToolUse hook: reading the observatory aftermath • Running PostToolUse hook: reading the observatory aftermath PostToolUse hook (blocked) warning: wizard-tower PostToolUse demo blocked the Bash follow-through on purpose. feedback: PostToolUse demo blocked the result after execution. Remove [block-post-tool-use] to continue. PostToolUse hook (completed) warning: wizard-tower PostToolUse demo inspected Bash: echo 'one' hook context: PostToolUse demo saw the Bash result. Response preview: one ────────────────────────────────────────────────────────────────────────────────────────────────────── • Ran them in parallel. Little wave report: - echo 'one' completed and returned one. - echo '[block-pre-tool-use]' was blocked before execution by the PreToolUse hook. - echo '[block-post-tool-use]' ran, but its result was blocked afterward by the PostToolUse hook. The hook messages were: - PreToolUse demo blocked the command. Remove [block-pre-tool-use] to continue. - PostToolUse demo blocked the result after execution. Remove [block-post-tool-use] to continue. › for the blocked messages (correctly blocked in this test) did the tool output land back into context? • Yeah, from this run: - PreToolUse block: the command itself did not execute, so no command output landed in context. Only the block message did. - PostToolUse block: the command did execute, but the actual stdout did not come back into context. Only the post-tool block message did. So the blocked notifications landed in context, but the blocked command payloads themselves did not. Nice clean guardrail, aloha. ```
166 lines
5.1 KiB
Rust
166 lines
5.1 KiB
Rust
pub(crate) mod command_runner;
|
|
pub(crate) mod config;
|
|
pub(crate) mod discovery;
|
|
pub(crate) mod dispatcher;
|
|
pub(crate) mod output_parser;
|
|
pub(crate) mod schema_loader;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use codex_config::ConfigLayerStack;
|
|
use codex_protocol::protocol::HookRunSummary;
|
|
|
|
use crate::events::post_tool_use::PostToolUseOutcome;
|
|
use crate::events::post_tool_use::PostToolUseRequest;
|
|
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;
|
|
use crate::events::stop::StopRequest;
|
|
use crate::events::user_prompt_submit::UserPromptSubmitOutcome;
|
|
use crate::events::user_prompt_submit::UserPromptSubmitRequest;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct CommandShell {
|
|
pub program: String,
|
|
pub args: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub(crate) struct ConfiguredHandler {
|
|
pub event_name: codex_protocol::protocol::HookEventName,
|
|
pub matcher: Option<String>,
|
|
pub command: String,
|
|
pub timeout_sec: u64,
|
|
pub status_message: Option<String>,
|
|
pub source_path: PathBuf,
|
|
pub display_order: i64,
|
|
}
|
|
|
|
impl ConfiguredHandler {
|
|
pub fn run_id(&self) -> String {
|
|
format!(
|
|
"{}:{}:{}",
|
|
self.event_name_label(),
|
|
self.display_order,
|
|
self.source_path.display()
|
|
)
|
|
}
|
|
|
|
fn event_name_label(&self) -> &'static str {
|
|
match self.event_name {
|
|
codex_protocol::protocol::HookEventName::PreToolUse => "pre-tool-use",
|
|
codex_protocol::protocol::HookEventName::PostToolUse => "post-tool-use",
|
|
codex_protocol::protocol::HookEventName::SessionStart => "session-start",
|
|
codex_protocol::protocol::HookEventName::UserPromptSubmit => "user-prompt-submit",
|
|
codex_protocol::protocol::HookEventName::Stop => "stop",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct ClaudeHooksEngine {
|
|
handlers: Vec<ConfiguredHandler>,
|
|
warnings: Vec<String>,
|
|
shell: CommandShell,
|
|
}
|
|
|
|
impl ClaudeHooksEngine {
|
|
pub(crate) fn new(
|
|
enabled: bool,
|
|
config_layer_stack: Option<&ConfigLayerStack>,
|
|
shell: CommandShell,
|
|
) -> Self {
|
|
if !enabled {
|
|
return Self {
|
|
handlers: Vec::new(),
|
|
warnings: Vec::new(),
|
|
shell,
|
|
};
|
|
}
|
|
|
|
if cfg!(windows) {
|
|
return Self {
|
|
handlers: Vec::new(),
|
|
warnings: vec![
|
|
"Disabled `codex_hooks` for this session because `hooks.json` lifecycle hooks are not supported on Windows yet."
|
|
.to_string(),
|
|
],
|
|
shell,
|
|
};
|
|
}
|
|
|
|
let _ = schema_loader::generated_hook_schemas();
|
|
let discovered = discovery::discover_handlers(config_layer_stack);
|
|
Self {
|
|
handlers: discovered.handlers,
|
|
warnings: discovered.warnings,
|
|
shell,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn warnings(&self) -> &[String] {
|
|
&self.warnings
|
|
}
|
|
|
|
pub(crate) fn preview_session_start(
|
|
&self,
|
|
request: &SessionStartRequest,
|
|
) -> Vec<HookRunSummary> {
|
|
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) fn preview_post_tool_use(
|
|
&self,
|
|
request: &PostToolUseRequest,
|
|
) -> Vec<HookRunSummary> {
|
|
crate::events::post_tool_use::preview(&self.handlers, request)
|
|
}
|
|
|
|
pub(crate) async fn run_session_start(
|
|
&self,
|
|
request: SessionStartRequest,
|
|
turn_id: Option<String>,
|
|
) -> SessionStartOutcome {
|
|
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) async fn run_post_tool_use(
|
|
&self,
|
|
request: PostToolUseRequest,
|
|
) -> PostToolUseOutcome {
|
|
crate::events::post_tool_use::run(&self.handlers, &self.shell, request).await
|
|
}
|
|
|
|
pub(crate) fn preview_user_prompt_submit(
|
|
&self,
|
|
request: &UserPromptSubmitRequest,
|
|
) -> Vec<HookRunSummary> {
|
|
crate::events::user_prompt_submit::preview(&self.handlers, request)
|
|
}
|
|
|
|
pub(crate) async fn run_user_prompt_submit(
|
|
&self,
|
|
request: UserPromptSubmitRequest,
|
|
) -> UserPromptSubmitOutcome {
|
|
crate::events::user_prompt_submit::run(&self.handlers, &self.shell, request).await
|
|
}
|
|
|
|
pub(crate) fn preview_stop(&self, request: &StopRequest) -> Vec<HookRunSummary> {
|
|
crate::events::stop::preview(&self.handlers, request)
|
|
}
|
|
|
|
pub(crate) async fn run_stop(&self, request: StopRequest) -> StopOutcome {
|
|
crate::events::stop::run(&self.handlers, &self.shell, request).await
|
|
}
|
|
}
|