## Why
Guardian review analytics needs a Rust event shape that matches the
backend schema while avoiding unnecessary PII exposure from reviewed
tool calls. This PR narrows the analytics payload to the fields we
intend to emit and keeps shared Guardian assessment enums in protocol
instead of duplicating equivalent analytics-only enums.
## What changed
- Uses protocol Guardian enums directly for `risk_level`,
`user_authorization`, `outcome`, and command source values.
- Removes high-risk reviewed-action fields from the analytics payload,
including raw commands, display strings, working directories, file
paths, network targets/hosts, justification text, retry reason, and
rationale text.
- Makes `target_item_id` and `tool_call_count` nullable so the Codex
event can represent cases where the app-server protocol or producer does
not have those values.
- Keeps lower-risk structured reviewed-action metadata such as sandbox
permissions, permission profile, `tty`, `execve` source/program, network
protocol/port, and MCP connector/tool labels.
- Adds an analytics reducer/client test covering `codex_guardian_review`
serialization with an optional `target_item_id` and absent removed
fields.
## Verification
- `cargo test -p codex-analytics
guardian_review_event_ingests_custom_fact_with_optional_target_item`
- `cargo fmt --check`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/17692).
* #17696
* #17695
* #17693
* __->__ #17692
## 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>
# Why
Add product analytics for hook handler executions so we can understand
which hooks are running, where they came from, and whether they
completed, failed, stopped, or blocked work.
# What
- add the new `codex_hook_run` analytics event and payload plumbing in
`codex-rs/analytics`
- emit hook-run analytics from the shared hook completion path in
`codex-rs/core`
- classify hook source from the loaded hook path as `system`, `user`,
`project`, or `unknown`
```
{
"event_type": "codex_hook_run",
"event_params": {
"thread_id": "string",
"turn_id": "string",
"model_slug": "string",
"hook_name": "string, // any HookEventName
"hook_source": "system | user | project | unknown",
"status": "completed | failed | stopped | blocked"
}
}
```
---------
Co-authored-by: Codex <noreply@openai.com>
## Summary
Adds `thread_source` field to the existing Codex turn metadata sent to
Responses API
- Sends `thread_source: "user"` for user-initiated sessions: CLI, VS
Code, and Exec
- Sends `thread_source: "subagent"` for subagent sessions
- Omits `thread_source` for MCP, custom, and unknown session sources
- Uses the existing turn metadata transport:
- HTTP requests send through the `x-codex-turn-metadata` header
- WebSocket `response.create` requests send through
`client_metadata["x-codex-turn-metadata"]`
## Testing
- `cargo test -p codex-protocol
session_source_thread_source_name_classifies_user_and_subagent_sources`
- `cargo test -p codex-core turn_metadata_state`
- `cargo test -p codex-core --test responses_headers
responses_stream_includes_turn_metadata_header_for_git_workspace_e2e --
--nocapture`
## Summary
This PR adds the parent conversation/session id to the subagent-start
analytics event for Guardian subagents.
Previously, Guardian sessions were emitted as subagent
thread-initialized events, but their `parent_thread_id` was serialized
as `null`. After this change, the `codex_thread_initialized` analytics
event for a Guardian child session includes the parent user conversation
id.