mirror of
https://github.com/openai/codex.git
synced 2026-11-06 00:30:29 +00:00
Support PreToolUse updatedInput rewrites (#20527)
## Why
`PreToolUse` already exposes `updatedInput` in its hook output schema,
but Codex currently rejects it instead of applying the rewrite. That
leaves hook authors unable to make the documented pre-execution
adjustment to a tool call before it runs.
## What
- Accept `updatedInput` from `PreToolUse` hooks when paired with
`permissionDecision: "allow"`.
- Apply the rewritten input before dispatch so the tool executes the
updated payload, not the original one.
- Preserve the stable hook-facing compatibility shapes that
participating tool handlers expose:
- Bash-like tools (`shell`, `container.exec`, `local_shell`,
`shell_command`, `exec_command`) use `{ "command": ... }`.
- `apply_patch` exposes its patch body through the same command-shaped
hook contract.
- MCP tools expose their JSON argument object directly.
- Keep each participating tool handler responsible for translating
hook-facing `updatedInput` back into its concrete invocation shape.
## Verification
Direct Bash-like rewrite coverage:
- `pre_tool_use_rewrites_shell_before_execution`
- `pre_tool_use_rewrites_container_exec_before_execution`
- `pre_tool_use_rewrites_local_shell_before_execution`
- `pre_tool_use_rewrites_shell_command_before_execution`
- `pre_tool_use_rewrites_exec_command_before_execution`
These cases assert that each supported Bash-like surface runs only the
rewritten command while the hook still observes the original `{
"command": ... }` input.
`pre_tool_use_rewrites_apply_patch_before_execution`
- Model emits one patch.
- Hook swaps in a different patch.
- Asserts only the rewritten file is created, and the hook saw the
original patch.
`pre_tool_use_rewrites_code_mode_nested_exec_command_before_execution`
- Model runs one nested shell command from code mode.
- Hook rewrites it.
- Asserts only the rewritten command runs, and the hook saw the original
nested input.
`pre_tool_use_rewrites_mcp_tool_before_execution`
- Model calls the RMCP echo tool.
- Hook rewrites the MCP arguments.
- Asserts the MCP server receives and returns the rewritten message, not
the original one.
This commit is contained in:
1 parent
17ed5ad0b0
commit
d08906a944
22 files changed
+1021
-47
No files matched your search
@@ -14,6 +14,7 @@ use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::runtimes::shell::ShellRuntimeBackend;
|
||||
|
||||
use super::RunExecLikeArgs;
|
||||
use super::rewrite_shell_function_updated_hook_input;
|
||||
use super::run_exec_like;
|
||||
use super::shell_function_post_tool_use_payload;
|
||||
use super::shell_function_pre_tool_use_payload;
|
||||
@@ -46,6 +47,14 @@ impl ToolHandler for ContainerExecHandler {
|
||||
shell_function_pre_tool_use_payload(invocation)
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
rewrite_shell_function_updated_hook_input(invocation, updated_input, "container.exec")
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::updated_hook_command;
|
||||
use crate::tools::hook_names::HookToolName;
|
||||
use crate::tools::registry::PostToolUsePayload;
|
||||
use crate::tools::registry::PreToolUsePayload;
|
||||
@@ -64,6 +65,26 @@ impl ToolHandler for LocalShellHandler {
|
||||
})
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
mut invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
let command = updated_hook_command(&updated_input)?;
|
||||
invocation.payload = match invocation.payload {
|
||||
ToolPayload::LocalShell { mut params } => {
|
||||
params.command = shlex::split(command).ok_or_else(|| {
|
||||
FunctionCallError::RespondToModel(
|
||||
"hook returned shell input with an invalid command string".to_string(),
|
||||
)
|
||||
})?;
|
||||
ToolPayload::LocalShell { params }
|
||||
}
|
||||
payload => payload,
|
||||
};
|
||||
Ok(invocation)
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
|
||||
@@ -17,6 +17,8 @@ use crate::tools::context::ToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments_with_base_path;
|
||||
use crate::tools::handlers::resolve_workdir_base_path;
|
||||
use crate::tools::handlers::rewrite_function_string_argument;
|
||||
use crate::tools::handlers::updated_hook_command;
|
||||
use crate::tools::hook_names::HookToolName;
|
||||
use crate::tools::registry::PostToolUsePayload;
|
||||
use crate::tools::registry::PreToolUsePayload;
|
||||
@@ -175,6 +177,27 @@ impl ToolHandler for ShellCommandHandler {
|
||||
})
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
mut invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
let ToolPayload::Function { arguments } = invocation.payload else {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"hook input rewrite received unsupported shell_command payload".to_string(),
|
||||
));
|
||||
};
|
||||
invocation.payload = ToolPayload::Function {
|
||||
arguments: rewrite_function_string_argument(
|
||||
&arguments,
|
||||
"shell_command",
|
||||
"command",
|
||||
updated_hook_command(&updated_input)?,
|
||||
)?,
|
||||
};
|
||||
Ok(invocation)
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
|
||||
@@ -22,6 +22,7 @@ use codex_tools::ToolSpec;
|
||||
use super::super::shell_spec::ShellToolOptions;
|
||||
use super::super::shell_spec::create_shell_tool;
|
||||
use super::RunExecLikeArgs;
|
||||
use super::rewrite_shell_function_updated_hook_input;
|
||||
use super::run_exec_like;
|
||||
use super::shell_function_post_tool_use_payload;
|
||||
use super::shell_function_pre_tool_use_payload;
|
||||
@@ -95,6 +96,14 @@ impl ToolHandler for ShellHandler {
|
||||
shell_function_pre_tool_use_payload(invocation)
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
rewrite_shell_function_updated_hook_input(invocation, updated_input, "shell")
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
|
||||
Reference in new issue
Block a user