mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
Restore thread-owned permissions on cold resume (#53150)
## Why Cold resume can replace saved unnamed permissions with current configuration defaults or recover an executor's permissions from turn context. A first-turn context can also predate an acknowledged settings update. ## What changed - Restore eligible unnamed permission profiles from settings snapshots owned by the resumed thread, honoring explicit caller overrides and current managed requirements. - Use owned snapshots as the authority for named permission profiles and reapply workspace permissions for the resumed workspace roots. - Persist a thread-owned settings checkpoint after the first turn's context, serialized with settings updates so older checkpoints cannot supersede newer settings. ## Testing Extend regression coverage for cold resume with changed defaults, managed permission restrictions, workspace-root overrides, foreign snapshots, and settings updates during first-turn processing. GitOrigin-RevId: e411d9fd3d586a4162498c9c77c4184459071bd1
This commit is contained in:
@@ -4039,7 +4039,7 @@ impl ThreadRequestProcessor {
|
||||
}
|
||||
|
||||
// Copied or referenced history can contain another thread's settings. Only snapshots
|
||||
// explicitly owned by this thread can override its startup cwd and workspace folders.
|
||||
// explicitly owned by this thread can restore its permissions, cwd, and workspace folders.
|
||||
let history_settings = if let InitialHistory::Resumed(resumed) = &thread_history {
|
||||
resumed.history.iter().rev().find_map(|item| match item {
|
||||
RolloutItem::EventMsg(EventMsg::ThreadSettingsApplied(event))
|
||||
@@ -4131,9 +4131,31 @@ impl ThreadRequestProcessor {
|
||||
}
|
||||
let has_explicit_model_resume_override =
|
||||
has_model_resume_override(request_overrides.as_ref(), &typesafe_overrides);
|
||||
let persisted_permission_profile = history_settings
|
||||
.filter(|settings| {
|
||||
settings.active_permission_profile.is_none()
|
||||
&& !has_permission_override(request_overrides.as_ref(), &typesafe_overrides)
|
||||
&& !request_overrides.as_ref().is_some_and(|overrides| {
|
||||
overrides.keys().any(|key| {
|
||||
key == "sandbox_workspace_write"
|
||||
|| key.starts_with("sandbox_workspace_write.")
|
||||
})
|
||||
})
|
||||
})
|
||||
.map(|settings| settings.permission_profile.clone());
|
||||
let mut persisted_settings = match &thread_history {
|
||||
InitialHistory::Resumed(resumed) => latest_persisted_resume_settings(&resumed.history),
|
||||
InitialHistory::New | InitialHistory::Cleared | InitialHistory::Forked(_) => None,
|
||||
};
|
||||
// A TurnContext can describe an executor's profile, so an owned snapshot
|
||||
// is authoritative for named permissions too.
|
||||
if let (Some(settings), Some(snapshot)) = (&mut persisted_settings, history_settings) {
|
||||
settings.active_permission_profile = snapshot.active_permission_profile.clone();
|
||||
}
|
||||
let persisted_metadata = self
|
||||
.load_and_apply_persisted_resume_metadata(
|
||||
&thread_history,
|
||||
persisted_settings.as_ref(),
|
||||
&mut request_overrides,
|
||||
&mut typesafe_overrides,
|
||||
)
|
||||
@@ -4147,12 +4169,7 @@ impl ThreadRequestProcessor {
|
||||
history_cwd: history_cwd.clone(),
|
||||
workspace_roots: typesafe_overrides.workspace_roots.clone(),
|
||||
persisted_metadata,
|
||||
persisted_settings: match &thread_history {
|
||||
InitialHistory::Resumed(resumed) => {
|
||||
latest_persisted_resume_settings(&resumed.history)
|
||||
}
|
||||
InitialHistory::New | InitialHistory::Cleared | InitialHistory::Forked(_) => None,
|
||||
},
|
||||
persisted_settings,
|
||||
};
|
||||
let mut config = match prepared_config.take() {
|
||||
Some(prepared) if prepared.state == config_state => prepared.config,
|
||||
@@ -4181,18 +4198,18 @@ impl ThreadRequestProcessor {
|
||||
|
||||
match self
|
||||
.thread_manager
|
||||
.resume_thread_with_history(
|
||||
config,
|
||||
thread_history,
|
||||
self.auth_manager.clone(),
|
||||
match target {
|
||||
.start_thread(StartThreadOptions {
|
||||
initial_history: thread_history,
|
||||
persisted_permission_profile,
|
||||
parent_trace: match target {
|
||||
ThreadResumeTarget::Client(request_id) => {
|
||||
self.request_trace_context(request_id).await
|
||||
}
|
||||
ThreadResumeTarget::DaemonRecovery(_) => None,
|
||||
},
|
||||
client_mcp_extensions,
|
||||
)
|
||||
..StartThreadOptions::new(config)
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(NewThread {
|
||||
@@ -4441,14 +4458,14 @@ impl ThreadRequestProcessor {
|
||||
async fn load_and_apply_persisted_resume_metadata(
|
||||
&self,
|
||||
thread_history: &InitialHistory,
|
||||
persisted_settings: Option<&PersistedResumeSettings>,
|
||||
request_overrides: &mut Option<HashMap<String, serde_json::Value>>,
|
||||
typesafe_overrides: &mut ConfigOverrides,
|
||||
) -> Option<ThreadMetadata> {
|
||||
let InitialHistory::Resumed(resumed_history) = thread_history else {
|
||||
return None;
|
||||
};
|
||||
if let Some(persisted_settings) = latest_persisted_resume_settings(&resumed_history.history)
|
||||
{
|
||||
if let Some(persisted_settings) = persisted_settings {
|
||||
if typesafe_overrides.approval_policy.is_none() {
|
||||
typesafe_overrides.approval_policy = Some(persisted_settings.approval_policy);
|
||||
}
|
||||
@@ -4462,7 +4479,8 @@ impl ThreadRequestProcessor {
|
||||
if !has_permission_override(request_overrides.as_ref(), typesafe_overrides) {
|
||||
typesafe_overrides.persisted_permission_profile_id = persisted_settings
|
||||
.active_permission_profile
|
||||
.map(|profile| profile.id);
|
||||
.as_ref()
|
||||
.map(|profile| profile.id.clone());
|
||||
}
|
||||
}
|
||||
let state_db_ctx = self.state_db.clone()?;
|
||||
|
||||
Reference in New Issue
Block a user