4 Commits

  • Add subagent identity to hook inputs (#22882)
    # What
    
    When a normal hook fires inside a thread-spawned subagent, Codex now
    includes these optional top-level fields in the hook input:
    
    - `agent_id`: the child thread id
    - `agent_type`: the subagent role
    
    Root-agent hook inputs omit these fields. `SubagentStart` and
    `SubagentStop` keep their existing required `agent_id` and `agent_type`
    fields because those events are inherently subagent-scoped.
    
    This does not change matcher behavior. Tool hooks still match on tool
    name, compact hooks still match on trigger, and `UserPromptSubmit` still
    ignores matchers. Only `SubagentStart` and `SubagentStop` match on
    `agent_type`.
  • Support MCP tools in hooks (#18385)
    ## Summary
    
    Lifecycle hooks currently treat `PreToolUse`, `PostToolUse`, and
    `PermissionRequest` as Bash-only flows
    - hook schema constrains `tool_name` to `Bash`
    - hook input assumes a command-shaped `tool_input`
    - core hook dispatch path passes only shell command strings
    
    That means hooks cannot target MCP tools even though MCP tool names are
    model-visible and stable
    
    This change generalizes those hook paths so they can match and receive
    payloads for MCP tools while preserving the existing Bash behavior.
    
    ## Reviewer Notes
    
    I think these are the key files
    - `codex-rs/core/src/tools/handlers/mcp.rs`
    - `codex-rs/core/src/mcp_tool_call.rs`
    
    Otherwise the changes across apply_patch, shell, and unified_exec are
    mainly to rewire everything to be `tool_input` based instead of just
    `command` so that it'll make sense for MCP tools.
    
    ## Changes
    
    - Allow `PreToolUse`, `PostToolUse`, and `PermissionRequest` hook inputs
    to carry arbitrary `tool_name` and `tool_input` values instead of
    hard-coding `Bash` and command-only payloads.
    - Add MCP hook payload support through `McpHandler`, using the
    model-visible tool name from `ToolInvocation` and the raw MCP arguments
    as `tool_input`.
    - Include MCP tool responses in `PostToolUse` by serializing
    `McpToolOutput` into the hook response payload.
    - Run `PermissionRequest` hooks for MCP approval requests after
    remembered approval checks and before falling back to user-facing MCP
    elicitation.
    - Preserve exact matching for literal hook matchers like `Bash` and
    `mcp__memory__create_entities`, while keeping regex matcher support for
    patterns like `mcp__memory__.*` and `mcp__.*__write.*`.
    
    ---------
    
    Co-authored-by: Andrei Eternal <eternal@openai.com>
    Co-authored-by: Codex <noreply@openai.com>
  • fix(core): emit hooks for apply_patch edits (#18391)
    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.
  • Add PermissionRequest hooks support (#17563)
    ## Why
    
    We need `PermissionRequest` hook support!
    
    Also addresses:
    - https://github.com/openai/codex/issues/16301
    - run a script on Hook to do things like play a sound to draw attention
    but actually no-op so user can still approve
    - can omit the `decision` object from output or just have the script
    exit 0 and print nothing
    - https://github.com/openai/codex/issues/15311
      - let the script approve/deny on its own
      - external UI what will run on Hook and relay decision back to codex
    
    
    ## Reviewer Note
    
    There's a lot of plumbing for the new hook, key files to review are:
    - New hook added in `codex-rs/hooks/src/events/permission_request.rs`
    - Wiring for network approvals
    `codex-rs/core/src/tools/network_approval.rs`
    - Wiring for tool orchestrator `codex-rs/core/src/tools/orchestrator.rs`
    - Wiring for execve
    `codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs`
    
    ## What
    
    - Wires shell, unified exec, and network approval prompts into the
    `PermissionRequest` hook flow.
    - Lets hooks allow or deny approval prompts; quiet or invalid hooks fall
    back to the normal approval path.
    - Uses `tool_input.description` for user-facing context when it helps:
      - shell / `exec_command`: the request justification, when present
      - network approvals: `network-access <domain>`
    - Uses `tool_name: Bash` for shell, unified exec, and network approval
    permission-request hooks.
    - For network approvals, passes the originating command in
    `tool_input.command` when there is a single owning call; otherwise falls
    back to the synthetic `network-access ...` command.
    
    <details>
    <summary>Example `PermissionRequest` hook input for a shell
    approval</summary>
    
    ```json
    {
      "session_id": "<session-id>",
      "turn_id": "<turn-id>",
      "transcript_path": "/path/to/transcript.jsonl",
      "cwd": "/path/to/cwd",
      "hook_event_name": "PermissionRequest",
      "model": "gpt-5",
      "permission_mode": "default",
      "tool_name": "Bash",
      "tool_input": {
        "command": "rm -f /tmp/example"
      }
    }
    ```
    
    </details>
    
    <details>
    <summary>Example `PermissionRequest` hook input for an escalated
    `exec_command` request</summary>
    
    ```json
    {
      "session_id": "<session-id>",
      "turn_id": "<turn-id>",
      "transcript_path": "/path/to/transcript.jsonl",
      "cwd": "/path/to/cwd",
      "hook_event_name": "PermissionRequest",
      "model": "gpt-5",
      "permission_mode": "default",
      "tool_name": "Bash",
      "tool_input": {
        "command": "cp /tmp/source.json /Users/alice/export/source.json",
        "description": "Need to copy a generated file outside the workspace"
      }
    }
    ```
    
    </details>
    
    <details>
    <summary>Example `PermissionRequest` hook input for a network
    approval</summary>
    
    ```json
    {
      "session_id": "<session-id>",
      "turn_id": "<turn-id>",
      "transcript_path": "/path/to/transcript.jsonl",
      "cwd": "/path/to/cwd",
      "hook_event_name": "PermissionRequest",
      "model": "gpt-5",
      "permission_mode": "default",
      "tool_name": "Bash",
      "tool_input": {
        "command": "curl http://codex-network-test.invalid",
        "description": "network-access http://codex-network-test.invalid"
      }
    }
    ```
    
    </details>
    
    ## Follow-ups
    
    - Implement the `PermissionRequest` semantics for `updatedInput`,
    `updatedPermissions`, `interrupt`, and suggestions /
    `permission_suggestions`
    - Add `PermissionRequest` support for the `request_permissions` tool
    path
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>