Skip to content

app-server: keep thread PermissionProfile immutable#21250

Open
bolinfest wants to merge 1 commit into
mainfrom
pr21250
Open

app-server: keep thread PermissionProfile immutable#21250
bolinfest wants to merge 1 commit into
mainfrom
pr21250

Conversation

@bolinfest
Copy link
Copy Markdown
Collaborator

@bolinfest bolinfest commented May 5, 2026

Why

Codex is moving permission state from the legacy SandboxPolicy abstraction to PermissionProfile. For app-server threads, the effective PermissionProfile value should be durable thread state: app-server clients should not be able to submit arbitrary replacement permission profiles through resume, fork, or turn APIs.

The pieces that still need to be mutable are separate from that client-supplied value. A client can select a named profile by id, and the server resolves that id against local config before updating the thread's active profile name and effective server-owned profile value. A client can also update the thread's workspace roots independently. Keeping those concerns separate removes profile overlays such as ActivePermissionProfileModification and prevents SandboxPolicy::WorkspaceWrite from carrying a second copy of workspace roots.

What Changed

App-server API behavior

  • permissions selection is now just a profile id string at the v2 boundary; unknown ids return a JSON-RPC error before a turn starts.
  • Existing app-server threads preserve their persisted PermissionProfile unless the client selects a valid server-known profile id, in which case the server applies the resolved profile value together with the active profile name.
  • workspaceRoots can be updated independently from cwd; cwd is only a workspace root when it appears explicitly in the root list.
  • Thread start/resume/fork responses continue to include the effective permissionProfile for exact remote observability, plus activePermissionProfile identity metadata and the legacy sandbox projection for compatibility.
  • codex exec resume now omits workspaceRoots unless the invocation explicitly uses --add-dir, so ordinary exec resumes preserve the roots stored with the rollout instead of replacing them with the current process config.

Thread state, rollout compatibility, and memories

  • workspace_roots are persisted with thread/session state and replayed through turn context, rollout reconstruction, and thread-store metadata.
  • Old rollouts that serialized SandboxPolicy::WorkspaceWrite.writable_roots are migrated on deserialize so their extra writable roots are not lost after SandboxPolicy stops carrying that field.
  • Explicit serialized workspace_roots continue to win over any legacy sandbox-derived roots.
  • The Codex memories directory remains an internal writable root on the effective permission profile, so memory read/write behavior does not depend on the user-visible workspace_roots list and still works when clients pass explicit workspaceRoots.
  • Status and sandbox-summary code hide CODEX_HOME/memories from user-facing workspace-root summaries, so enabling memories does not make /status report the memories store as the active workspace.

Sandbox, status, and model-facing summaries

  • SandboxPolicy::WorkspaceWrite no longer owns writable_roots; sandbox setup receives workspace roots from current thread/session state instead.
  • Sandbox summaries now go through the PermissionProfile summary path with explicit workspace roots, so /status and related CLI/TUI surfaces show roots added with --add-dir.
  • Internal memory roots stay available to Codex without being shown as user-facing workspace roots in status output.
  • Model-facing workspace-write permissions text now describes the configured writable roots instead of saying cwd is inherently writable.
  • The old summarize_sandbox_policy path and TUI permission compatibility shim were removed where they are no longer needed.

Cleanup and generated API artifacts

  • Removed ActivePermissionProfileModification, PermissionProfileModificationParams, and PermissionProfileSelectionParams from protocol models and generated TypeScript exports.
  • Regenerated app-server JSON schema and TypeScript fixtures for the v2 API changes.
  • Updated app-server docs to describe server-owned thread permissions, mutable active profile names, and explicit workspace root updates.
  • Isolated the app-server trimmed-skills warning test from real $HOME/.agents/skills so the expected omitted-skill count remains deterministic.

Test Strategy

The main regression risks are not whether the workspace compiles, but whether permission/root state changes only through the intended API surfaces and whether old persisted state keeps replaying correctly. I focused the targeted tests around those invariants:

  • API shape and rejection behavior: codex-rs/app-server-protocol/src/protocol/v2/tests.rs::turn_start_permissions_uses_profile_id_string_shape verifies permissions is a bare profile id string. sandbox_policy_rejects_legacy_workspace_write_writable_roots_field verifies removed workspaceWrite.writableRoots payloads are rejected instead of silently ignored.
  • Turn and resume mutation semantics: codex-rs/app-server/tests/suite/v2/turn_start.rs::turn_start_rejects_unknown_permission_selection_before_starting_turn covers unknown profile ids. turn_start_updates_cwd_without_replacing_workspace_roots_v2 covers the important split between changing cwd, preserving workspaceRoots, and selecting :workspace. codex-rs/app-server/tests/suite/v2/thread_resume.rs::thread_resume_running_applies_workspace_roots_and_active_profile_name covers live resume updates to roots and active profile name.
  • Lower-level thread/session invariants: codex-rs/core/src/session/tests.rs::session_configuration_apply_preserves_legacy_workspace_roots_on_cwd_update and session_configuration_apply_preserves_active_permission_profile_on_legacy_cwd_update protect the turn-context behavior that app-server ultimately relies on.
  • Persisted state and rollout compatibility: codex-rs/app-server/src/request_processors/thread_processor_tests.rs::persisted_thread_permission_state_uses_latest_turn_active_profile and persisted_thread_permission_state_preserves_empty_workspace_roots_from_event_roundtrip cover reconstruction of durable profile/root state from stored history.
  • Memories and user-visible summaries: codex-rs/core/src/config/config_tests.rs::workspace_write_always_includes_memories_root_once verifies the internal memories root remains writable. codex-rs/utils/sandbox-summary/src/sandbox_summary.rs::workspace_write_summary_hides_internal_writable_roots and codex-rs/tui/src/status/tests.rs::status_permissions_workspace_profile_hides_internal_memories_root verify that root does not leak into /status or sandbox summaries.
  • Workspace-root display and model-facing context: codex-rs/utils/sandbox-summary/src/sandbox_summary.rs::workspace_write_summary_includes_workspace_roots_and_network_access, codex-rs/tui/src/status/tests.rs::status_permissions_named_workspace_profile_shows_workspace_roots, and codex-rs/core/src/context/permissions_instructions_tests.rs::renders_sandbox_mode_text cover the user/model text surfaces affected by removing roots from SandboxPolicy.
  • codex exec resume: codex-rs/exec/src/lib_tests.rs::thread_resume_params_include_workspace_roots_only_when_explicit verifies ordinary exec resumes preserve rollout roots, while explicit --add-dir resumes still send replacement roots. session_configured_from_thread_response_uses_workspace_roots_from_response and session_configured_from_thread_response_preserves_empty_workspace_roots verify exec trusts the server response exactly, including explicit empty roots. session_configured_from_thread_response_prefers_exact_permission_profile and session_configured_from_thread_resume_response_prefers_exact_permission_profile verify exec keeps the exact v2 permissionProfile instead of reconstructing a lossy legacy sandbox projection.

@bolinfest bolinfest requested a review from a team as a code owner May 5, 2026 21:45
@bolinfest bolinfest changed the title Move workspace roots onto thread/session state and stop using active permission profile modifications as an overlay for app-server: keep thread PermissionProfile immutable May 5, 2026
Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 11750256e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/protocol/src/permissions.rs
Comment thread codex-rs/exec/src/lib.rs Outdated
Copy link
Copy Markdown
Contributor

@evawong-oai evawong-oai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@bolinfest bolinfest force-pushed the pr21250 branch 25 times, most recently from 3eace94 to ea13af2 Compare May 11, 2026 22:06
@bolinfest bolinfest force-pushed the pr21250 branch 8 times, most recently from 014c589 to 0e2d80b Compare May 12, 2026 00:20
@bolinfest bolinfest requested a review from viyatb-oai May 12, 2026 00:21
@@ -203,14 +210,9 @@ pub struct ThreadStartResponse {
/// Reviewer currently used for approval requests on this thread.
pub approvals_reviewer: ApprovalsReviewer,
/// Legacy sandbox policy retained for compatibility. Experimental clients
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] This drops the exact active permissionProfile from thread start/resume/fork responses and leaves remote clients with only the legacy sandbox projection plus profile identity metadata. The remote TUI then rebuilds its local PermissionProfile from SandboxPolicy (app_server_session.rs), but that projection is lossy for profiles that do not round-trip through to_legacy_sandbox_policy and instead hit the compatibility workspace-write fallback. In that case the server keeps the exact profile while the TUI syncs a coarser approximation into local session/config state, so /status and any later UI logic reading config.permissions.permission_profile() can be wrong. Can we keep returning the effective permissionProfile here, or replace it with an equally expressive canonical field? Immutability does not require dropping observability.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in d824faf: v2 thread start/resume/fork responses now return the effective permissionProfile again, and the remote TUI now prefers that exact response profile instead of rebuilding from the lossy legacy sandbox projection. Added coverage for the remote preference path as well.

…permission profile modifications as an overlay for writable roots. Existing app-server threads no longer accept arbitrary PermissionProfile or SandboxPolicy replacements; permissions requests select a server-known profile id and apply the resolved server-owned profile together with active profile metadata. Workspace roots can be updated independently, and SandboxPolicy::WorkspaceWrite no longer stores its own writable_roots.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants