Honor prior cancellation before starting Codex delegates (#51063)

## Why

An already-cancelled delegate spawn can report a runtime admission or startup error instead of `TurnAborted`, including when runtime shutdown has been requested.

## What changed

Check the cancellation token before runtime admission and session startup so prior cancellation returns `TurnAborted` immediately after approval-policy validation.

## Testing

Extend the pre-cancelled spawn test to cover both normal operation and requested runtime shutdown, asserting `TurnAborted` within a one-second timeout.

GitOrigin-RevId: 5a25a211acdd5c246a3c638243ccc8b7240fd051
This commit is contained in:
jif
2026-10-05 09:50:51 +00:00
committed by copyberry
parent 435d1b3f19
commit cacdc46619
2 changed files with 43 additions and 30 deletions
+4
View File
@@ -67,6 +67,10 @@ pub(crate) async fn run_codex_thread_interactive(
"Codex delegates require approval policy `never`".to_string(),
));
}
// Do not let admission or a ready startup error win over prior cancellation.
if cancel_token.is_cancelled() {
return Err(CodexErr::TurnAborted);
}
config.permissions.approval_policy = Constrained::allow_only(AskForApproval::Never);
config.model_provider.supports_websockets &= parent_session
.services
+39 -30
View File
@@ -187,38 +187,47 @@ async fn forward_ops_preserves_submission_trace_context() {
#[tokio::test]
async fn run_codex_thread_interactive_respects_pre_cancelled_spawn() {
let (parent_session, parent_ctx, _rx_events) =
crate::session::tests::make_session_and_context_with_rx().await;
let mut config = parent_ctx.config.as_ref().clone();
config.permissions.approval_policy = Constrained::allow_only(AskForApproval::Never);
let cancel_token = CancellationToken::new();
cancel_token.cancel();
let parent_environments = parent_ctx.initial_environments.clone();
for shutdown_requested in [false, true] {
let (parent_session, parent_ctx, _rx_events) =
crate::session::tests::make_session_and_context_with_rx().await;
if shutdown_requested {
parent_session
.services
.local_agent_runtime
.request_shutdown();
}
let mut config = parent_ctx.config.as_ref().clone();
config.permissions.approval_policy = Constrained::allow_only(AskForApproval::Never);
let cancel_token = CancellationToken::new();
cancel_token.cancel();
let parent_environments = parent_ctx.initial_environments.clone();
let result = timeout(
Duration::from_secs(/*secs*/ 1),
run_codex_thread_interactive(
config,
Arc::clone(&parent_session.services.auth_manager),
Arc::clone(&parent_session.services.models_manager),
parent_session,
parent_ctx,
parent_environments,
cancel_token,
SubAgentSource::Review,
codex_extension_api::SessionIsolation::Inherit,
/*initial_history*/ None,
crate::session::GitEnrichmentPolicy::Fresh,
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
),
)
.await
.expect("cancelled delegate spawn should not hang");
let result = timeout(
Duration::from_secs(/*secs*/ 1),
run_codex_thread_interactive(
config,
Arc::clone(&parent_session.services.auth_manager),
Arc::clone(&parent_session.services.models_manager),
parent_session,
parent_ctx,
parent_environments,
cancel_token,
SubAgentSource::Review,
codex_extension_api::SessionIsolation::Inherit,
/*initial_history*/ None,
crate::session::GitEnrichmentPolicy::Fresh,
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
),
)
.await
.expect("cancelled delegate spawn should not hang");
assert!(matches!(
result,
Err(err) if matches!(err.details(), CodexErrorDetails::TurnAborted)
));
let error = result.err().expect("cancelled delegate spawn should fail");
assert!(
matches!(error.details(), CodexErrorDetails::TurnAborted),
"unexpected cancelled delegate spawn error (shutdown_requested={shutdown_requested}): {error}"
);
}
}
#[tokio::test]