Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
67a8037
initial
eternal-openai Apr 14, 2026
5948090
Merge remote-tracking branch 'origin/main' into hooks_unified_exec
eternal-openai Apr 21, 2026
34514df
codex: fix CI failure on PR #18888
eternal-openai Apr 21, 2026
70a373a
core: simplify unified exec hook command state
eternal-openai Apr 21, 2026
21637b1
initial impl
abhinav-oai Apr 17, 2026
7009fc8
clean up display_command
abhinav-oai Apr 17, 2026
a2b9627
pass ToolInvocation
abhinav-oai Apr 17, 2026
3c1825b
Preserve Bash permission hook payload shape
abhinav-oai Apr 17, 2026
a4c2437
codex: fix CI failure on PR #18385
abhinav-oai Apr 17, 2026
c94ce27
Add MCP hook integration tests
abhinav-oai Apr 18, 2026
7e61709
Add MCP permission hook integration test
abhinav-oai Apr 18, 2026
e19fceb
Remove duplicate MCP permission hook suite test
abhinav-oai Apr 21, 2026
d2d08bd
Fix MCP hook payloads
abhinav-oai Apr 22, 2026
114db76
Move pre-tool hook block message formatting
abhinav-oai Apr 22, 2026
9d32374
Fix hook payload rebase fallout
abhinav-oai Apr 22, 2026
e944ffe
Merge origin/main into hooks_unified_exec
eternal-openai Apr 22, 2026
adb59fd
Merge remote-tracking branch 'origin/eternal/hooks_unified_exec_post_…
abhinav-oai Apr 22, 2026
c9dfe04
Use invocation metadata for post tool hooks
abhinav-oai Apr 22, 2026
6f7f133
Simplify MCP post-hook tool input capture
abhinav-oai Apr 22, 2026
4248d43
Use invocation tool name for MCP hooks
abhinav-oai Apr 22, 2026
e58dfb7
Simplify approved MCP tool execution
abhinav-oai Apr 22, 2026
7a78f15
Merge remote-tracking branch 'origin/main' into abhinav/hooks-mcp-too…
abhinav-oai Apr 23, 2026
41123b0
Merge branch 'main' into abhinav/hooks-mcp-tools-support
abhinav-oai Apr 23, 2026
b010ef3
Restore realtime delegated shell timeout
abhinav-oai Apr 23, 2026
e4bb5b4
Merge branch 'main' into abhinav/hooks-mcp-tools-support
abhinav-oai Apr 23, 2026
9986073
Fix hook engine test request payloads
abhinav-oai Apr 23, 2026
c81fbb1
Fix MCP hook tests on Windows
abhinav-oai Apr 23, 2026
348318a
Merge remote-tracking branch 'origin/main' into abhinav/hooks-mcp-too…
abhinav-oai Apr 23, 2026
a8a7c9e
Fix Windows MCP hook test command
abhinav-oai Apr 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion codex-rs/app-server/tests/suite/v2/realtime_conversation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ use wiremock::matchers::path;
use wiremock::matchers::path_regex;

const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DELEGATED_SHELL_TOOL_TIMEOUT_MS: u64 = 30_000;
const STARTUP_CONTEXT_HEADER: &str = "Startup context from Codex.";
const V2_STEERING_ACKNOWLEDGEMENT: &str =
"This was sent to steer the previous background agent task.";
Expand Down Expand Up @@ -1781,7 +1782,9 @@ async fn webrtc_v2_tool_call_delegated_turn_can_execute_shell_tool() -> Result<(
create_shell_command_sse_response(
realtime_tool_ok_command(),
/*workdir*/ None,
Some(5000),
// Windows CI can spend several seconds starting the nested PowerShell command. This
// test verifies delegated shell-tool plumbing, not timeout enforcement.
Some(DELEGATED_SHELL_TOOL_TIMEOUT_MS),
"shell_call",
)?,
create_final_assistant_message_sse_response("shell tool finished")?,
Expand Down
38 changes: 26 additions & 12 deletions codex-rs/core/src/hook_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use crate::context::HookAdditionalContext;
use crate::event_mapping::parse_turn_item;
use crate::session::session::Session;
use crate::session::turn_context::TurnContext;
use crate::tools::hook_names::HookToolName;
use crate::tools::sandboxing::PermissionRequestPayload;

pub(crate) struct HookRuntimeOutcome {
Expand Down Expand Up @@ -137,9 +138,8 @@ pub(crate) async fn run_pre_tool_use_hooks(
sess: &Arc<Session>,
turn_context: &Arc<TurnContext>,
tool_use_id: String,
tool_name: String,
matcher_aliases: Vec<String>,
command: String,
tool_name: &HookToolName,
tool_input: &Value,
) -> Option<String> {
let request = PreToolUseRequest {
session_id: sess.conversation_id,
Expand All @@ -148,10 +148,10 @@ pub(crate) async fn run_pre_tool_use_hooks(
transcript_path: sess.hook_transcript_path().await,
model: turn_context.model_info.slug.clone(),
permission_mode: hook_permission_mode(turn_context),
tool_name,
matcher_aliases,
tool_name: tool_name.name().to_string(),
matcher_aliases: tool_name.matcher_aliases().to_vec(),
tool_use_id,
command,
tool_input: tool_input.clone(),
};
let preview_runs = sess.hooks().preview_pre_tool_use(&request);
emit_hook_started_events(sess, turn_context, preview_runs).await;
Expand All @@ -163,7 +163,22 @@ pub(crate) async fn run_pre_tool_use_hooks(
} = sess.hooks().run_pre_tool_use(request).await;
emit_hook_completed_events(sess, turn_context, hook_events).await;

if should_block { block_reason } else { None }
if should_block {
block_reason.map(|reason| {
if (tool_name.name() == "Bash" || tool_name.name() == "apply_patch")
&& let Some(command) = tool_input.get("command").and_then(Value::as_str)
{
format!("Command blocked by PreToolUse hook: {reason}. Command: {command}")
} else {
format!(
"Tool call blocked by PreToolUse hook: {reason}. Tool: {}",
tool_name.name()
)
}
})
} else {
None
}
}

// PermissionRequest hooks share the same preview/start/completed event flow as
Expand All @@ -185,8 +200,7 @@ pub(crate) async fn run_permission_request_hooks(
tool_name: payload.tool_name.name().to_string(),
matcher_aliases: payload.tool_name.matcher_aliases().to_vec(),
run_id_suffix: run_id_suffix.to_string(),
command: payload.command,
description: payload.description,
tool_input: payload.tool_input,
};
let preview_runs = sess.hooks().preview_permission_request(&request);
emit_hook_started_events(sess, turn_context, preview_runs).await;
Expand All @@ -202,7 +216,7 @@ pub(crate) async fn run_permission_request_hooks(

/// Runs matching `PostToolUse` hooks after a tool has produced a successful output.
///
/// The `tool_name`, matcher aliases, `command`, and `tool_response` values are
/// The `tool_name`, matcher aliases, `tool_input`, and `tool_response` values are
/// already adapted by the tool handler into the stable hook contract. Passing
/// raw internal tool data here would leak implementation details into user hook
/// matchers and hook logs.
Expand All @@ -212,7 +226,7 @@ pub(crate) async fn run_post_tool_use_hooks(
tool_use_id: String,
tool_name: String,
matcher_aliases: Vec<String>,
command: String,
tool_input: Value,
tool_response: Value,
) -> PostToolUseOutcome {
let request = PostToolUseRequest {
Expand All @@ -225,7 +239,7 @@ pub(crate) async fn run_post_tool_use_hooks(
tool_name,
matcher_aliases,
tool_use_id,
command,
tool_input,
tool_response,
};
let preview_runs = sess.hooks().preview_post_tool_use(&request);
Expand Down
Loading
Loading