[codex] Remove legacy after tool use hooks (#21805)

## Why

The legacy `AfterToolUse` hook path was still wired through core tool
dispatch even though the hooks registry never populated any handlers for
it. The supported hook surface is `PostToolUse`, so the old
infrastructure was dead code on the hot path.

## What changed

- Removed the legacy `AfterToolUse` dispatch from `codex-core` tool
execution.
- Removed the unused legacy hook payload types and exports from
`codex-hooks`.
- Simplified legacy notify handling now that `HookEvent` only carries
`AfterAgent`.

## Validation

- `cargo test -p codex-hooks`
- `cargo test -p codex-core registry`
This commit is contained in:
pakrym-oai
2026-05-08 13:20:05 -07:00
committed by GitHub
parent e783341b70
commit 46e2250bcf
5 changed files with 3 additions and 312 deletions

View File

@@ -37,9 +37,6 @@ pub fn legacy_notify_json(payload: &HookPayload) -> Result<String, serde_json::E
last_assistant_message: event.last_assistant_message.clone(),
})
}
HookEvent::AfterToolUse { .. } => Err(serde_json::Error::io(std::io::Error::other(
"legacy notify payload is only supported for after_agent",
))),
}
}

View File

@@ -69,13 +69,9 @@ pub use schema::write_schema_fixtures;
pub use types::Hook;
pub use types::HookEvent;
pub use types::HookEventAfterAgent;
pub use types::HookEventAfterToolUse;
pub use types::HookPayload;
pub use types::HookResponse;
pub use types::HookResult;
pub use types::HookToolInput;
pub use types::HookToolInputLocalShell;
pub use types::HookToolKind;
/// Returns the hook event label used in persisted hook-state keys.
pub fn hook_event_key_label(event_name: HookEventName) -> &'static str {

View File

@@ -46,7 +46,6 @@ pub struct HookListOutcome {
#[derive(Clone)]
pub struct Hooks {
after_agent: Vec<Hook>,
after_tool_use: Vec<Hook>,
engine: ClaudeHooksEngine,
}
@@ -76,7 +75,6 @@ impl Hooks {
);
Self {
after_agent,
after_tool_use: Vec::new(),
engine,
}
}
@@ -88,7 +86,6 @@ impl Hooks {
fn hooks_for_event(&self, hook_event: &HookEvent) -> &[Hook] {
match hook_event {
HookEvent::AfterAgent { .. } => &self.after_agent,
HookEvent::AfterToolUse { .. } => &self.after_tool_use,
}
}

View File

@@ -4,7 +4,6 @@ use chrono::DateTime;
use chrono::SecondsFormat;
use chrono::Utc;
use codex_protocol::ThreadId;
use codex_protocol::models::SandboxPermissions;
use codex_utils_absolute_path::AbsolutePathBuf;
use futures::future::BoxFuture;
use serde::Serialize;
@@ -81,62 +80,6 @@ pub struct HookEventAfterAgent {
pub last_assistant_message: Option<String>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum HookToolKind {
Function,
Custom,
LocalShell,
Mcp,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct HookToolInputLocalShell {
pub command: Vec<String>,
pub workdir: Option<String>,
pub timeout_ms: Option<u64>,
pub sandbox_permissions: Option<SandboxPermissions>,
pub prefix_rule: Option<Vec<String>>,
pub justification: Option<String>,
}
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(tag = "input_type", rename_all = "snake_case")]
pub enum HookToolInput {
Function {
arguments: String,
},
Custom {
input: String,
},
LocalShell {
params: HookToolInputLocalShell,
},
Mcp {
server: String,
tool: String,
arguments: String,
},
}
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct HookEventAfterToolUse {
pub turn_id: String,
pub call_id: String,
pub tool_name: String,
pub tool_kind: HookToolKind,
pub tool_input: HookToolInput,
pub executed: bool,
pub success: bool,
pub duration_ms: u64,
pub mutating: bool,
pub sandbox: String,
pub sandbox_policy: String,
pub output_preview: String,
}
fn serialize_triggered_at<S>(value: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
@@ -151,10 +94,6 @@ pub enum HookEvent {
#[serde(flatten)]
event: HookEventAfterAgent,
},
AfterToolUse {
#[serde(flatten)]
event: HookEventAfterToolUse,
},
}
#[cfg(test)]
@@ -162,7 +101,6 @@ mod tests {
use chrono::TimeZone;
use chrono::Utc;
use codex_protocol::ThreadId;
use codex_protocol::models::SandboxPermissions;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
use pretty_assertions::assert_eq;
@@ -170,11 +108,7 @@ mod tests {
use super::HookEvent;
use super::HookEventAfterAgent;
use super::HookEventAfterToolUse;
use super::HookPayload;
use super::HookToolInput;
use super::HookToolInputLocalShell;
use super::HookToolKind;
#[test]
fn hook_payload_serializes_stable_wire_shape() {
@@ -215,78 +149,4 @@ mod tests {
assert_eq!(actual, expected);
}
#[test]
fn after_tool_use_payload_serializes_stable_wire_shape() {
let session_id = ThreadId::new();
let cwd = test_path_buf("/tmp").abs();
let payload = HookPayload {
session_id,
cwd: cwd.clone(),
client: None,
triggered_at: Utc
.with_ymd_and_hms(2025, 1, 1, 0, 0, 0)
.single()
.expect("valid timestamp"),
hook_event: HookEvent::AfterToolUse {
event: HookEventAfterToolUse {
turn_id: "turn-2".to_string(),
call_id: "call-1".to_string(),
tool_name: "local_shell".to_string(),
tool_kind: HookToolKind::LocalShell,
tool_input: HookToolInput::LocalShell {
params: HookToolInputLocalShell {
command: vec!["cargo".to_string(), "fmt".to_string()],
workdir: Some("codex-rs".to_string()),
timeout_ms: Some(60_000),
sandbox_permissions: Some(SandboxPermissions::UseDefault),
justification: None,
prefix_rule: None,
},
},
executed: true,
success: true,
duration_ms: 42,
mutating: true,
sandbox: "none".to_string(),
sandbox_policy: "danger-full-access".to_string(),
output_preview: "ok".to_string(),
},
},
};
let actual = serde_json::to_value(payload).expect("serialize hook payload");
let expected = json!({
"session_id": session_id.to_string(),
"cwd": cwd.display().to_string(),
"triggered_at": "2025-01-01T00:00:00Z",
"hook_event": {
"event_type": "after_tool_use",
"turn_id": "turn-2",
"call_id": "call-1",
"tool_name": "local_shell",
"tool_kind": "local_shell",
"tool_input": {
"input_type": "local_shell",
"params": {
"command": ["cargo", "fmt"],
"workdir": "codex-rs",
"timeout_ms": 60000,
"sandbox_permissions": "use_default",
"justification": null,
"prefix_rule": null,
},
},
"executed": true,
"success": true,
"duration_ms": 42,
"mutating": true,
"sandbox": "none",
"sandbox_policy": "danger-full-access",
"output_preview": "ok",
},
});
assert_eq!(actual, expected);
}
}