mirror of
https://github.com/openai/codex.git
synced 2026-05-24 04:54:52 +00:00
Fixes https://github.com/openai/codex/issues/16732. ## Why `apply_patch` is Codex's primary file edit path, but it was not emitting `PreToolUse` or `PostToolUse` hook events. That meant hook-based policy, auditing, and write coordination could observe shell commands while missing the actual file mutation performed by `apply_patch`. The issue also exposed that the hook runtime serialized command hook payloads with `tool_name: "Bash"` unconditionally. Even if `apply_patch` supplied hook payloads, hooks would either fail to match it directly or receive misleading stdin that identified the edit as a Bash tool call. ## What Changed - Added `PreToolUse` and `PostToolUse` payload support to `ApplyPatchHandler`. - Exposed the raw patch body as `tool_input.command` for both JSON/function and freeform `apply_patch` calls. - Taught tool hook payloads to carry a handler-supplied hook-facing `tool_name`. - Preserved existing shell compatibility by continuing to emit `Bash` for shell-like tools. - Serialized the selected hook `tool_name` into hook stdin instead of hardcoding `Bash`. - Relaxed the generated hook command input schema so `tool_name` can represent tools other than `Bash`. ## Verification Added focused handler coverage for: - JSON/function `apply_patch` calls producing a `PreToolUse` payload. - Freeform `apply_patch` calls producing a `PreToolUse` payload. - Successful `apply_patch` output producing a `PostToolUse` payload. - Shell and `exec_command` handlers continuing to expose `Bash`. Added end-to-end hook coverage for: - A `PreToolUse` hook matching `^apply_patch$` blocking the patch before the target file is created. - A `PostToolUse` hook matching `^apply_patch$` receiving the patch input and tool response, then adding context to the follow-up model request. - Non-participating tools such as the plan tool continuing not to emit `PreToolUse`/`PostToolUse` hook events. Also validated manually with a live `codex exec` smoke test using an isolated temp workspace and temp `CODEX_HOME`. The smoke test confirmed that a real `apply_patch` edit emits `PreToolUse`/`PostToolUse` with `tool_name: "apply_patch"`, a shell command still emits `tool_name: "Bash"`, and a denying `PreToolUse` hook prevents the blocked patch file from being created.
56 lines
2.0 KiB
Rust
56 lines
2.0 KiB
Rust
//! Hook-facing tool names and matcher compatibility aliases.
|
|
//!
|
|
//! Hook stdin exposes one canonical `tool_name`, but matcher selection may also
|
|
//! need to recognize names from adjacent tool ecosystems. Keeping those two
|
|
//! concepts together prevents handlers from accidentally serializing a
|
|
//! compatibility alias, such as `Write`, as the stable hook payload name.
|
|
|
|
/// Identifies a tool in hook payloads and hook matcher selection.
|
|
///
|
|
/// `name` is the canonical value serialized into hook stdin. Matcher aliases are
|
|
/// internal-only compatibility names that may select the same hook handlers but
|
|
/// must not change the payload seen by hook processes.
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub(crate) struct HookToolName {
|
|
name: String,
|
|
matcher_aliases: Vec<String>,
|
|
}
|
|
|
|
impl HookToolName {
|
|
/// Builds a hook tool name with no matcher aliases.
|
|
pub(crate) fn new(name: impl Into<String>) -> Self {
|
|
Self {
|
|
name: name.into(),
|
|
matcher_aliases: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Returns the hook identity for file edits performed through `apply_patch`.
|
|
///
|
|
/// The serialized name remains `apply_patch` so logs and policies can key
|
|
/// off the actual Codex tool. `Write` and `Edit` are accepted as matcher
|
|
/// aliases for compatibility with hook configurations that describe edits
|
|
/// using Claude Code-style names.
|
|
pub(crate) fn apply_patch() -> Self {
|
|
Self {
|
|
name: "apply_patch".to_string(),
|
|
matcher_aliases: vec!["Write".to_string(), "Edit".to_string()],
|
|
}
|
|
}
|
|
|
|
/// Returns the hook identity historically used for shell-like tools.
|
|
pub(crate) fn bash() -> Self {
|
|
Self::new("Bash")
|
|
}
|
|
|
|
/// Returns the canonical hook name serialized into hook stdin.
|
|
pub(crate) fn name(&self) -> &str {
|
|
&self.name
|
|
}
|
|
|
|
/// Returns additional matcher inputs that should select the same handlers.
|
|
pub(crate) fn matcher_aliases(&self) -> &[String] {
|
|
&self.matcher_aliases
|
|
}
|
|
}
|