From a4cb97ba5a6d02cb066dfda73e7ea83823a55fea Mon Sep 17 00:00:00 2001 From: Shijie Rao Date: Wed, 21 Jan 2026 23:58:53 -0800 Subject: [PATCH] Chore: add cmd related info to exec approval request (#9659) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Summary We now rely purely on `item/commandExecution/requestApproval` item to render pending approval in VSCE and app. With v2 approach, it does not include the actual cmd that it is attempting and therefore we can only use `proposedExecpolicyAmendment` to render which can be incomplete. ### Reproduce * Add `prefix_rule(pattern=["echo"], decision="prompt")` to your `~/.codex/rules.default.rules`. * Ask to `Run echo "approval-test" please` in VSCE or app. * The pending approval protal does show up but with no content #### Example screenshot Screenshot 2026-01-21 at 8 23
25 PM #### Sample output ``` {"method":"item/commandExecution/requestApproval","id":0,"params":{ "threadId":"019be439-5a90-7600-a7ea-2d2dcc50302a", "turnId":"0", "itemId":"call_usgnQ4qEX5U9roNdjT7fPzhb", "reason":"`/bin/zsh -lc 'echo \"testing\"'` requires approval by policy", "proposedExecpolicyAmendment":null }} ``` ### Fix Inlude `command` string, `cwd` and `command_actions` in `CommandExecutionRequestApprovalParams` so that consumers can display the correct command instead of relying on exec policy output. --- codex-rs/app-server-protocol/src/protocol/v2.rs | 12 ++++++++++++ codex-rs/app-server-test-client/src/main.rs | 14 ++++++++++++++ codex-rs/app-server/README.md | 2 +- codex-rs/app-server/src/bespoke_event_handling.rs | 3 +++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 9007577112..0030092ffc 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -2263,6 +2263,18 @@ pub struct CommandExecutionRequestApprovalParams { pub item_id: String, /// Optional explanatory reason (e.g. request for network access). pub reason: Option, + /// The command to be executed. + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub command: Option, + /// The command's working directory. + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub cwd: Option, + /// Best-effort parsed command actions for friendly display. + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub command_actions: Option>, /// Optional proposed execpolicy amendment to allow similar commands without prompting. pub proposed_execpolicy_amendment: Option, } diff --git a/codex-rs/app-server-test-client/src/main.rs b/codex-rs/app-server-test-client/src/main.rs index 1407c2401d..1e2b94bd8f 100644 --- a/codex-rs/app-server-test-client/src/main.rs +++ b/codex-rs/app-server-test-client/src/main.rs @@ -842,6 +842,9 @@ impl CodexClient { turn_id, item_id, reason, + command, + cwd, + command_actions, proposed_execpolicy_amendment, } = params; @@ -851,6 +854,17 @@ impl CodexClient { if let Some(reason) = reason.as_deref() { println!("< reason: {reason}"); } + if let Some(command) = command.as_deref() { + println!("< command: {command}"); + } + if let Some(cwd) = cwd.as_ref() { + println!("< cwd: {}", cwd.display()); + } + if let Some(command_actions) = command_actions.as_ref() + && !command_actions.is_empty() + { + println!("< command actions: {command_actions:?}"); + } if let Some(execpolicy_amendment) = proposed_execpolicy_amendment.as_ref() { println!("< proposed execpolicy amendment: {execpolicy_amendment:?}"); } diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 470581fe4f..92d3cae049 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -445,7 +445,7 @@ Certain actions (shell commands or modifying files) may require explicit user ap Order of messages: 1. `item/started` — shows the pending `commandExecution` item with `command`, `cwd`, and other fields so you can render the proposed action. -2. `item/commandExecution/requestApproval` (request) — carries the same `itemId`, `threadId`, `turnId`, optionally `reason` or `risk`, plus `parsedCmd` for friendly display. +2. `item/commandExecution/requestApproval` (request) — carries the same `itemId`, `threadId`, `turnId`, optionally `reason`, plus `command`, `cwd`, and `commandActions` for friendly display. 3. Client response — `{ "decision": "accept", "acceptSettings": { "forSession": false } }` or `{ "decision": "decline" }`. 4. `item/completed` — final `commandExecution` item with `status: "completed" | "failed" | "declined"` and execution output. Render this as the authoritative result. diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index f9332e6f61..931c1073aa 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -241,6 +241,9 @@ pub(crate) async fn apply_bespoke_event_handling( // and emit the corresponding EventMsg, we repurpose the call_id as the item_id. item_id: item_id.clone(), reason, + command: Some(command_string.clone()), + cwd: Some(cwd.clone()), + command_actions: Some(command_actions.clone()), proposed_execpolicy_amendment: proposed_execpolicy_amendment_v2, }; let rx = outgoing