Files
codex/codex-rs/network-proxy/src/proxy/execution_scope.rs
T
jif 4e688da437 Skip the network denial grace period when remote decisions drain (#59960)
## Why

Completed remote commands wait up to 100 ms for late network denials even when all execution-owned network decision callbacks and requests have finished.

## What changed

Allow deferred network approval cleanup to finish as soon as an executor-owned remote proxy's decisions drain. Retain the grace period when a shared listener could still report an untagged denial, including when that listener starts during the command. Always register the blocked-request observer when starting the session proxy.

## Testing

Extend the paused-time regression test to cover immediate completion after remote callbacks drain and propagation of a late denial from a newly registered shared listener.

GitOrigin-RevId: 41bda6504f6d97d5626beba58cf30fb4a9a2dbdd
2026-11-06 19:18:02 +00:00

65 lines
2.4 KiB
Rust

use super::*;
pub(super) struct ExecutionScope {
pub(super) environment_id: String,
pub(super) execution_id: String,
pub(super) attribution_token: String,
pub(super) environment_policy: Option<crate::EnvironmentNetworkPolicy>,
// Dropping the execution scope closes this channel and cancels remote reviews.
pub(super) lifetime_tx: tokio::sync::watch::Sender<()>,
state: Arc<NetworkProxyState>,
}
impl Drop for ExecutionScope {
fn drop(&mut self) {
self.state.unregister_execution(&self.attribution_token);
}
}
impl NetworkProxy {
/// Waits until every remote decision callback and in-flight request for this
/// execution has been dropped. Call after the executor has started, when no
/// further callbacks will be installed for this scope. An unscoped proxy
/// cannot establish this boundary, so its wait remains pending.
pub async fn wait_for_remote_policy_deciders(&self) {
match &self.execution_scope {
Some(scope) => scope.lifetime_tx.closed().await,
None => std::future::pending().await,
}
}
/// Returns a proxy with execution-specific attribution and attachment policy.
pub fn for_execution(
&self,
environment_id: &str,
execution_id: &str,
attribution_token: String,
environment_policy: Option<crate::EnvironmentNetworkPolicy>,
fallback_policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
) -> Result<Self> {
anyhow::ensure!(
self.execution_scope.is_none(),
"cannot scope an execution-scoped network proxy"
);
self.state
.register_execution(&attribution_token, environment_id, execution_id);
let (lifetime_tx, _) = tokio::sync::watch::channel(());
let mut proxy = self.clone();
proxy.policy_decider = proxy.policy_decider.or(fallback_policy_decider);
// Strict attachment allowlists cannot be expanded through approval callbacks.
if matches!(&environment_policy, Some(policy) if policy.managed_allowed_domains_only) {
proxy.policy_decider = None;
}
proxy.execution_scope = Some(Arc::new(ExecutionScope {
environment_id: environment_id.to_string(),
execution_id: execution_id.to_string(),
attribution_token,
environment_policy,
lifetime_tx,
state: Arc::clone(&self.state),
}));
Ok(proxy)
}
}