exec-server: cancel pending startup approval on terminate

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
starr-openai
2026-04-07 16:02:01 -07:00
parent 689107592d
commit ee3276c01d
2 changed files with 103 additions and 3 deletions

View File

@@ -587,14 +587,22 @@ impl LocalProcess {
self.require_initialized_for("exec")?;
let _process_id = params.process_id.clone();
let running = {
let process_map = self.inner.processes.lock().await;
match process_map.get(&params.process_id) {
let mut process_map = self.inner.processes.lock().await;
match process_map.get_mut(&params.process_id) {
Some(ProcessEntry::Running(process)) => {
if process.exit_code.is_some() {
if process.exit_code.is_some() || process.closed {
return Ok(TerminateResponse { running: false });
}
if let Some(session) = process.session.as_ref() {
session.terminate();
} else {
process.pending_exec_approval = None;
process.pending_start = None;
process.failure = Some("terminated before process start".to_string());
process.exit_code = Some(1);
process.closed = true;
process.output_notify.notify_waiters();
let _ = process.wake_tx.send(process.next_seq);
}
true
}

View File

@@ -232,3 +232,95 @@ async fn startup_exec_approval_decline_returns_failure_without_spawning() {
handler.shutdown().await;
}
#[tokio::test]
async fn startup_exec_approval_terminate_cancels_pending_start() {
let handler = initialized_handler().await;
let mut params = exec_params("proc-terminated");
params.argv = vec![
"bash".to_string(),
"-lc".to_string(),
"printf should-not-run".to_string(),
];
params.startup_exec_approval = Some(crate::protocol::ExecApprovalRequest {
call_id: "call-terminate".to_string(),
approval_id: None,
turn_id: "turn-terminate".to_string(),
command: vec![
"bash".to_string(),
"-lc".to_string(),
"printf should-not-run".to_string(),
],
cwd: std::env::current_dir().expect("cwd"),
reason: Some("approval required".to_string()),
additional_permissions: None,
proposed_execpolicy_amendment: None,
available_decisions: Some(vec![CommandExecutionApprovalDecision::Accept]),
});
handler.exec(params).await.expect("start process");
let pending = handler
.exec_read(ReadParams {
process_id: ProcessId::from("proc-terminated"),
after_seq: None,
max_bytes: None,
wait_ms: Some(0),
})
.await
.expect("read pending approval");
assert!(pending.exec_approval.is_some());
assert_eq!(
handler
.terminate(TerminateParams {
process_id: ProcessId::from("proc-terminated"),
})
.await
.expect("terminate response"),
TerminateResponse { running: true }
);
let cancelled = handler
.exec_read(ReadParams {
process_id: ProcessId::from("proc-terminated"),
after_seq: None,
max_bytes: None,
wait_ms: Some(0),
})
.await
.expect("read cancelled process");
assert_eq!(cancelled.chunks, Vec::new());
assert_eq!(
cancelled.failure.as_deref(),
Some("terminated before process start")
);
assert!(cancelled.exec_approval.is_none());
assert!(cancelled.closed);
assert_eq!(cancelled.exit_code, Some(1));
let error = handler
.resolve_exec_approval(ResolveExecApprovalParams {
process_id: ProcessId::from("proc-terminated"),
approval_id: "call-terminate".to_string(),
decision: CommandExecutionApprovalDecision::Accept,
})
.await
.expect_err("terminated process should not accept approval");
assert_eq!(error.code, -32600);
assert_eq!(
error.message,
"process id proc-terminated has no pending exec approval"
);
assert_eq!(
handler
.terminate(TerminateParams {
process_id: ProcessId::from("proc-terminated"),
})
.await
.expect("second terminate response"),
TerminateResponse { running: false }
);
handler.shutdown().await;
}