mirror of
https://github.com/openai/codex.git
synced 2026-09-10 20:26:47 +00:00
## 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>
103 lines
4.5 KiB
Rust
103 lines
4.5 KiB
Rust
use std::sync::OnceLock;
|
|
|
|
use serde_json::Value;
|
|
|
|
#[allow(dead_code)]
|
|
pub(crate) struct GeneratedHookSchemas {
|
|
pub post_tool_use_command_input: Value,
|
|
pub post_tool_use_command_output: Value,
|
|
pub permission_request_command_input: Value,
|
|
pub permission_request_command_output: Value,
|
|
pub pre_tool_use_command_input: Value,
|
|
pub pre_tool_use_command_output: Value,
|
|
pub session_start_command_input: Value,
|
|
pub session_start_command_output: Value,
|
|
pub user_prompt_submit_command_input: Value,
|
|
pub user_prompt_submit_command_output: Value,
|
|
pub stop_command_input: Value,
|
|
pub stop_command_output: Value,
|
|
}
|
|
|
|
pub(crate) fn generated_hook_schemas() -> &'static GeneratedHookSchemas {
|
|
static SCHEMAS: OnceLock<GeneratedHookSchemas> = OnceLock::new();
|
|
SCHEMAS.get_or_init(|| GeneratedHookSchemas {
|
|
post_tool_use_command_input: parse_json_schema(
|
|
"post-tool-use.command.input",
|
|
include_str!("../../schema/generated/post-tool-use.command.input.schema.json"),
|
|
),
|
|
post_tool_use_command_output: parse_json_schema(
|
|
"post-tool-use.command.output",
|
|
include_str!("../../schema/generated/post-tool-use.command.output.schema.json"),
|
|
),
|
|
permission_request_command_input: parse_json_schema(
|
|
"permission-request.command.input",
|
|
include_str!("../../schema/generated/permission-request.command.input.schema.json"),
|
|
),
|
|
permission_request_command_output: parse_json_schema(
|
|
"permission-request.command.output",
|
|
include_str!("../../schema/generated/permission-request.command.output.schema.json"),
|
|
),
|
|
pre_tool_use_command_input: parse_json_schema(
|
|
"pre-tool-use.command.input",
|
|
include_str!("../../schema/generated/pre-tool-use.command.input.schema.json"),
|
|
),
|
|
pre_tool_use_command_output: parse_json_schema(
|
|
"pre-tool-use.command.output",
|
|
include_str!("../../schema/generated/pre-tool-use.command.output.schema.json"),
|
|
),
|
|
session_start_command_input: parse_json_schema(
|
|
"session-start.command.input",
|
|
include_str!("../../schema/generated/session-start.command.input.schema.json"),
|
|
),
|
|
session_start_command_output: parse_json_schema(
|
|
"session-start.command.output",
|
|
include_str!("../../schema/generated/session-start.command.output.schema.json"),
|
|
),
|
|
user_prompt_submit_command_input: parse_json_schema(
|
|
"user-prompt-submit.command.input",
|
|
include_str!("../../schema/generated/user-prompt-submit.command.input.schema.json"),
|
|
),
|
|
user_prompt_submit_command_output: parse_json_schema(
|
|
"user-prompt-submit.command.output",
|
|
include_str!("../../schema/generated/user-prompt-submit.command.output.schema.json"),
|
|
),
|
|
stop_command_input: parse_json_schema(
|
|
"stop.command.input",
|
|
include_str!("../../schema/generated/stop.command.input.schema.json"),
|
|
),
|
|
stop_command_output: parse_json_schema(
|
|
"stop.command.output",
|
|
include_str!("../../schema/generated/stop.command.output.schema.json"),
|
|
),
|
|
})
|
|
}
|
|
|
|
fn parse_json_schema(name: &str, schema: &str) -> Value {
|
|
serde_json::from_str(schema)
|
|
.unwrap_or_else(|err| panic!("invalid generated hooks schema {name}: {err}"))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::generated_hook_schemas;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
#[test]
|
|
fn loads_generated_hook_schemas() {
|
|
let schemas = generated_hook_schemas();
|
|
|
|
assert_eq!(schemas.post_tool_use_command_input["type"], "object");
|
|
assert_eq!(schemas.post_tool_use_command_output["type"], "object");
|
|
assert_eq!(schemas.permission_request_command_input["type"], "object");
|
|
assert_eq!(schemas.permission_request_command_output["type"], "object");
|
|
assert_eq!(schemas.pre_tool_use_command_input["type"], "object");
|
|
assert_eq!(schemas.pre_tool_use_command_output["type"], "object");
|
|
assert_eq!(schemas.session_start_command_input["type"], "object");
|
|
assert_eq!(schemas.session_start_command_output["type"], "object");
|
|
assert_eq!(schemas.user_prompt_submit_command_input["type"], "object");
|
|
assert_eq!(schemas.user_prompt_submit_command_output["type"], "object");
|
|
assert_eq!(schemas.stop_command_input["type"], "object");
|
|
assert_eq!(schemas.stop_command_output["type"], "object");
|
|
}
|
|
}
|