Skip to content

Support PreToolUse updatedInput rewrites#20527

Merged
abhinav-oai merged 29 commits into
mainfrom
abhinav/hooks-updated-input
May 12, 2026
Merged

Support PreToolUse updatedInput rewrites#20527
abhinav-oai merged 29 commits into
mainfrom
abhinav/hooks-updated-input

Conversation

@abhinav-oai
Copy link
Copy Markdown
Collaborator

@abhinav-oai abhinav-oai commented Apr 30, 2026

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.

@abhinav-oai abhinav-oai requested a review from a team as a code owner April 30, 2026 23:35
Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 61724ae083

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/core/src/tools/events.rs Outdated
Comment thread codex-rs/core/src/tools/orchestrator.rs Outdated
Comment thread codex-rs/core/src/tools/network_approval.rs Outdated
@abhinav-oai abhinav-oai changed the title Support updatedInput hook rewrites Support PreToolUse updatedInput rewrites May 6, 2026
@abhinav-oai
Copy link
Copy Markdown
Collaborator Author

@codex review

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@abhinav-oai
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

let results = dispatcher::execute_handlers(
shell,
matched,
input_json,

P1 Badge Re-run policy hooks against rewritten input

All matched PreToolUse hooks are launched with the single input_json serialized from the original request, and only after they finish is one updated_input selected. This lets one hook rewrite a command after other PreToolUse policy hooks have inspected only the old command, so the executed input can bypass blockers that would have denied the rewritten payload.


let payload_for_response = invocation.payload.clone();
let log_payload = payload_for_response.log_payload();

P2 Badge Log the rewritten tool payload

log_payload is captured before PreToolUse can replace invocation.payload, but the later telemetry call logs this stale payload while the rewritten invocation is executed. Audit/telemetry records can therefore show a harmless original command even when hooks caused a different command or patch to run.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@abhinav-oai
Copy link
Copy Markdown
Collaborator Author

abhinav-oai commented May 6, 2026

let results = dispatcher::execute_handlers(
shell,
matched,
input_json,

P1 Badge Re-run policy hooks against rewritten input

I think this is working as intended under the trust model for hooks

  • updatedInput is only produced by hooks the user has explicitly trusted and opted into
  • so permissionDecision: "allow" with an updatedInput is the hook author saying "allow this call, but execute the rewritten payload instead."

Re-running the same trusted PreToolUse hook set against that rewritten payload would add another pass, but it would not strengthen the boundary we are relying on here as the rewrite itself came from trusted hook code.

let call_id_owned = invocation.call_id.clone();
let otel = invocation.turn.session_telemetry.clone();
let payload_for_response = invocation.payload.clone();
let log_payload = payload_for_response.log_payload();
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shifted this down so we log the rewritten tool payload

@abhinav-oai
Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown
Contributor

Codex Review: Didn't find any major issues. Chef's kiss.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@abhinav-oai
Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown
Contributor

Codex Review: Didn't find any major issues. Delightful!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Contributor

@eternal-openai eternal-openai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, one substantial concern and two minor ones:

  1. so an MCP tool like mcp__foo__exec_command has name = "exec_command" -> since we only switch on the name, we could mistake this MCP tool as being the real builtin one
  2. hooks spec says that when multiple PreToolUse hooks rewrite input, the last one to finish wins, with nondeterministic ordering because the hooks run in parallel. This implementation instead makes the last configured hook always win, which is a spec mismatch
  3. since the PR description says we only support updatedInput paired with "allow", bare "allow" should still be rejected until the actual allow semantics are implemented

Comment thread codex-rs/core/src/hook_runtime/tool_compat.rs Outdated
@abhinav-oai abhinav-oai requested a review from eternal-openai May 8, 2026 18:37
…-input

# Conflicts:
#	codex-rs/core/src/tools/handlers/apply_patch.rs
#	codex-rs/core/src/tools/handlers/apply_patch_tests.rs
#	codex-rs/core/src/tools/handlers/mcp.rs
#	codex-rs/core/src/tools/registry.rs
…-input

# Conflicts:
#	codex-rs/core/src/tools/handlers/apply_patch.rs
#	codex-rs/core/src/tools/handlers/apply_patch_tests.rs
#	codex-rs/core/src/tools/handlers/mcp.rs
#	codex-rs/core/src/tools/registry.rs
@abhinav-oai abhinav-oai enabled auto-merge (squash) May 12, 2026 02:14
@abhinav-oai abhinav-oai dismissed eternal-openai’s stale review May 12, 2026 02:27

addressed feedback

@abhinav-oai abhinav-oai merged commit d08906a into main May 12, 2026
27 checks passed
@abhinav-oai abhinav-oai deleted the abhinav/hooks-updated-input branch May 12, 2026 02:27
@github-actions github-actions Bot locked and limited conversation to collaborators May 12, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants