fix remaining unified exec lifecycle blockers

This commit is contained in:
Kyle Brown
2026-06-17 21:59:41 +00:00
parent b601f2aa81
commit 1884f253da
4 changed files with 58 additions and 3 deletions
+1
View File
@@ -56,6 +56,7 @@ pub(crate) fn set_deterministic_process_ids_for_tests(enabled: bool) {
pub(crate) use errors::UnifiedExecError;
pub(crate) use process::NoopSpawnLifecycle;
#[cfg(unix)]
pub(crate) use process::SpawnLifecycle;
pub(crate) use process::SpawnLifecycleHandle;
pub(crate) use process::UnifiedExecProcess;
+1 -1
View File
@@ -857,7 +857,7 @@ async fn remote_interrupt_failure_does_not_mark_lifecycle_cancelled() -> anyhow:
process: Arc::clone(&process),
call_id: "call".to_string(),
process_id,
cwd,
cwd: cwd.into(),
initial_exec_command_active: Arc::new(std::sync::atomic::AtomicBool::new(false)),
hook_command: "sleep 60".to_string(),
tty: false,
@@ -251,6 +251,7 @@ impl UnifiedExecProcess {
.map_err(|err| UnifiedExecError::process_failed(err.to_string()))?;
}
}
self.notify_lifecycle_cancelled();
self.signal_exit(self.exit_code());
self.finish_termination();
Ok(())
@@ -332,6 +333,8 @@ impl UnifiedExecProcess {
} else {
snippet
};
let state = self.state_rx.borrow().clone();
let _ = self.state_tx.send_replace(state.failed(message.clone()));
return Err(UnifiedExecError::sandbox_denied(message, exec_output));
}
Ok(())
@@ -2,11 +2,13 @@ use super::process::NoopSpawnLifecycle;
use super::process::SpawnLifecycle;
use super::process::UnifiedExecProcess;
use crate::unified_exec::UnifiedExecError;
use codex_exec_server::ExecOutputStream;
use codex_exec_server::ExecProcess;
use codex_exec_server::ExecProcessEventReceiver;
use codex_exec_server::ExecProcessFuture;
use codex_exec_server::ExecServerError;
use codex_exec_server::ProcessId;
use codex_exec_server::ProcessOutputChunk;
use codex_exec_server::ProcessSignal;
use codex_exec_server::ReadResponse;
use codex_exec_server::StartedExecProcess;
@@ -260,9 +262,11 @@ async fn noop_spawn_lifecycle_preserves_process_behavior() {
#[tokio::test]
async fn remote_terminate_confirmed_updates_state_on_success_only() {
let process = remote_process(
let (failed_state, lifecycle) = recording_lifecycle();
let process = remote_process_with_lifecycle(
WriteStatus::Accepted,
Some("terminate unavailable".to_string()),
lifecycle,
)
.await;
@@ -273,8 +277,15 @@ async fn remote_terminate_confirmed_updates_state_on_success_only() {
assert!(matches!(err, UnifiedExecError::ProcessFailed { .. }));
assert!(!process.has_exited());
assert!(!failed_state.cancelled.load(Ordering::SeqCst));
let process = remote_process(WriteStatus::Accepted, /*terminate_error*/ None).await;
let (succeeded_state, lifecycle) = recording_lifecycle();
let process = remote_process_with_lifecycle(
WriteStatus::Accepted,
/*terminate_error*/ None,
lifecycle,
)
.await;
process
.terminate_confirmed()
@@ -282,6 +293,46 @@ async fn remote_terminate_confirmed_updates_state_on_success_only() {
.expect("terminate should succeed");
assert!(process.has_exited());
assert!(succeeded_state.cancelled.load(Ordering::SeqCst));
}
#[tokio::test]
async fn sandbox_denied_early_exit_finishes_failed() {
let (state, lifecycle) = recording_lifecycle();
let (wake_tx, _wake_rx) = watch::channel(0);
let started = StartedExecProcess {
process: Arc::new(MockExecProcess {
process_id: "test-process".to_string().into(),
write_response: WriteResponse {
status: WriteStatus::Accepted,
},
read_responses: Mutex::new(VecDeque::from([ReadResponse {
chunks: vec![ProcessOutputChunk {
seq: 1,
stream: ExecOutputStream::Stderr,
chunk: b"Operation not permitted".to_vec().into(),
}],
next_seq: 2,
exited: true,
exit_code: Some(1),
closed: true,
failure: None,
}])),
terminate_error: None,
wake_tx,
}),
};
let err =
UnifiedExecProcess::from_exec_server_started(started, SandboxType::LinuxSeccomp, lifecycle)
.await
.expect_err("sandbox denial should fail startup");
assert!(matches!(err, UnifiedExecError::SandboxDenied { .. }));
assert_eq!(
*state.finishes.lock().expect("finish state"),
vec![(Some(1), true)]
);
}
#[tokio::test]