feat(core): plumb distinct approval ids for command approvals (#12051)

zsh fork PR stack:
- https://github.com/openai/codex/pull/12051 👈 
- https://github.com/openai/codex/pull/12052

With upcoming support for a fork of zsh that allows us to intercept
`execve` and run execpolicy checks for each subcommand as part of a
`CommandExecution`, it will be possible for there to be multiple
approval requests for a shell command like `/path/to/zsh -lc 'git status
&& rg \"TODO\" src && make test'`.

To support that, this PR introduces a new `approval_id` field across
core, protocol, and app-server so that we can associate approvals
properly for subcommands.
This commit is contained in:
Owen Lin
2026-02-18 01:55:57 +00:00
committed by GitHub
parent b3a8571219
commit db4d2599b5
33 changed files with 331 additions and 114 deletions
+8 -4
View File
@@ -2155,7 +2155,7 @@ impl Session {
/// Emit an exec approval request event and await the user's decision.
///
/// The request is keyed by `call_id` so matching responses are delivered
/// The request is keyed by `call_id` + `approval_id` so matching responses are delivered
/// to the correct in-flight turn. If the task is aborted, this returns the
/// default `ReviewDecision` (`Denied`).
#[allow(clippy::too_many_arguments)]
@@ -2163,32 +2163,36 @@ impl Session {
&self,
turn_context: &TurnContext,
call_id: String,
approval_id: Option<String>,
command: Vec<String>,
cwd: PathBuf,
reason: Option<String>,
network_approval_context: Option<NetworkApprovalContext>,
proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
) -> ReviewDecision {
// command-level approvals use `call_id`.
// `approval_id` is only present for subcommand callbacks (execve intercept)
let effective_approval_id = approval_id.clone().unwrap_or_else(|| call_id.clone());
// Add the tx_approve callback to the map before sending the request.
let (tx_approve, rx_approve) = oneshot::channel();
let approval_id = call_id.clone();
let prev_entry = {
let mut active = self.active_turn.lock().await;
match active.as_mut() {
Some(at) => {
let mut ts = at.turn_state.lock().await;
ts.insert_pending_approval(approval_id.clone(), tx_approve)
ts.insert_pending_approval(effective_approval_id.clone(), tx_approve)
}
None => None,
}
};
if prev_entry.is_some() {
warn!("Overwriting existing pending approval for call_id: {approval_id}");
warn!("Overwriting existing pending approval for call_id: {effective_approval_id}");
}
let parsed_cmd = parse_command(&command);
let event = EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent {
call_id,
approval_id,
turn_id: turn_context.sub_id.clone(),
command,
cwd,
+11 -4
View File
@@ -311,8 +311,10 @@ async fn handle_exec_approval(
event: ExecApprovalRequestEvent,
cancel_token: &CancellationToken,
) {
let approval_id_for_op = event.effective_approval_id();
let ExecApprovalRequestEvent {
call_id,
approval_id,
command,
cwd,
reason,
@@ -320,23 +322,28 @@ async fn handle_exec_approval(
proposed_execpolicy_amendment,
..
} = event;
let approval_id = call_id.clone();
// Race approval with cancellation and timeout to avoid hangs.
let approval_fut = parent_session.request_command_approval(
parent_ctx,
call_id,
approval_id,
command,
cwd,
reason,
network_approval_context,
proposed_execpolicy_amendment,
);
let decision =
await_approval_with_cancel(approval_fut, parent_session, &approval_id, cancel_token).await;
let decision = await_approval_with_cancel(
approval_fut,
parent_session,
&approval_id_for_op,
cancel_token,
)
.await;
let _ = codex
.submit(Op::ExecApproval {
id: approval_id,
id: approval_id_for_op,
turn_id: Some(turn_id),
decision,
})
@@ -247,6 +247,7 @@ impl NetworkApprovalService {
.request_command_approval(
turn_context.as_ref(),
attempt.call_id.clone(),
None,
attempt.command.clone(),
attempt.cwd.clone(),
Some(format!(
@@ -109,6 +109,7 @@ impl Approvable<ShellRequest> for ShellRuntime {
.request_command_approval(
turn,
call_id,
None,
command,
cwd,
reason,
@@ -110,6 +110,7 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
.request_command_approval(
turn,
call_id,
None,
command,
cwd,
reason,