fix(subagents) share execpolicy by default (#13702)

## Summary
If a subagent requests approval, and the user persists that approval to
the execpolicy, it should (by default) propagate. We'll need to rethink
this a bit in light of coming Permissions changes, though I think this
is closer to the end state that we'd want, which is that execpolicy
changes to one permissions profile should be synced across threads.

## Testing
- [x] Added integration test

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Dylan Hurd
2026-03-17 23:42:26 -07:00
committed by GitHub
parent a3613035f3
commit 84f4e7b39d
10 changed files with 427 additions and 13 deletions

View File

@@ -107,6 +107,9 @@ impl AgentControl {
let inherited_shell_snapshot = self
.inherited_shell_snapshot_for_source(&state, session_source.as_ref())
.await;
let inherited_exec_policy = self
.inherited_exec_policy_for_source(&state, session_source.as_ref(), &config)
.await;
let session_source = match session_source {
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id,
@@ -189,6 +192,7 @@ impl AgentControl {
session_source,
/*persist_extended_history*/ false,
inherited_shell_snapshot,
inherited_exec_policy,
)
.await?
} else {
@@ -200,6 +204,7 @@ impl AgentControl {
/*persist_extended_history*/ false,
/*metrics_service_name*/ None,
inherited_shell_snapshot,
inherited_exec_policy,
)
.await?
}
@@ -271,6 +276,9 @@ impl AgentControl {
let inherited_shell_snapshot = self
.inherited_shell_snapshot_for_source(&state, Some(&session_source))
.await;
let inherited_exec_policy = self
.inherited_exec_policy_for_source(&state, Some(&session_source), &config)
.await;
let rollout_path =
find_thread_path_by_id_str(config.codex_home.as_path(), &thread_id.to_string())
.await?
@@ -283,6 +291,7 @@ impl AgentControl {
self.clone(),
session_source,
inherited_shell_snapshot,
inherited_exec_policy,
)
.await?;
reservation.commit(resumed_thread.thread_id);
@@ -499,6 +508,30 @@ impl AgentControl {
let parent_thread = state.get_thread(*parent_thread_id).await.ok()?;
parent_thread.codex.session.user_shell().shell_snapshot()
}
async fn inherited_exec_policy_for_source(
&self,
state: &Arc<ThreadManagerState>,
session_source: Option<&SessionSource>,
child_config: &crate::config::Config,
) -> Option<Arc<crate::exec_policy::ExecPolicyManager>> {
let Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id, ..
})) = session_source
else {
return None;
};
let parent_thread = state.get_thread(*parent_thread_id).await.ok()?;
let parent_config = parent_thread.codex.session.get_config().await;
if !crate::exec_policy::child_uses_parent_exec_policy(&parent_config, child_config) {
return None;
}
Some(Arc::clone(
&parent_thread.codex.session.services.exec_policy,
))
}
}
#[cfg(test)]
#[path = "control_tests.rs"]