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:
Eric Traut
2026-10-11 15:48:35 +00:00
committed by copyberry
parent 97c1f62898
commit 7c9bfb910e
8 changed files with 208 additions and 57 deletions
+6
View File
@@ -304,6 +304,9 @@ pub struct StartThreadOptions {
pub reserved_thread_id: Option<ThreadId>,
/// Initial thread-owned plugin selection; omission restores persisted settings.
pub disabled_plugin_ids: Option<Vec<String>>,
/// Unnamed permissions recovered from an owned rollout snapshot on resume.
/// The session applies these only if current managed requirements allow them.
pub persisted_permission_profile: Option<codex_protocol::models::PermissionProfile>,
}
impl StartThreadOptions {
@@ -328,6 +331,7 @@ impl StartThreadOptions {
client_mcp_extensions: ClientMcpExtensions::default(),
reserved_thread_id: None,
disabled_plugin_ids: None,
persisted_permission_profile: None,
}
}
}
@@ -2100,6 +2104,7 @@ impl ThreadManagerState {
client_mcp_extensions,
mut reserved_thread_id,
disabled_plugin_ids,
persisted_permission_profile,
} = options;
let inherited_environments = captured_environments.or(inherited_environments);
let session_source = session_source.unwrap_or_else(|| self.session_source.clone());
@@ -2320,6 +2325,7 @@ impl ThreadManagerState {
conversation_history: initial_history,
history_initialization,
disabled_plugin_ids,
persisted_permission_profile,
requested_history_mode: history_mode,
fork_persistence,
// Keep only the manager registration internal. The session and its saved