Skip to content

permissions: expose active profile metadata#20095

Merged
bolinfest merged 1 commit intomainfrom
pr20095
Apr 30, 2026
Merged

permissions: expose active profile metadata#20095
bolinfest merged 1 commit intomainfrom
pr20095

Conversation

@bolinfest
Copy link
Copy Markdown
Collaborator

@bolinfest bolinfest commented Apr 28, 2026

Why

PermissionProfile is the canonical runtime permissions payload, but it intentionally describes the compiled permissions rather than where those permissions came from. That makes it awkward for clients to show a concise, stable label like :workspace or a user-defined profile name without reverse-engineering the profile shape.

This PR adds explicit sidecar metadata for the selected profile:

pub struct ActivePermissionProfile {
    pub id: String,
    pub extends: Option<String>,
    pub modifications: Vec<ActivePermissionProfileModification>,
}

pub enum ActivePermissionProfileModification {
    AdditionalWritableRoot { path: AbsolutePathBuf },
}

The runtime still honors the exact PermissionProfile; ActivePermissionProfile exists for display/provenance. This lets clients render labels like Workspace, Read Only, or Profile safe-worktree without trying to infer those names from the compiled permissions. The extends field is reserved for future profile inheritance, and modifications currently records bounded user-requested changes such as additional writable roots.

The app-server request model also now reflects the direction we want for permissions: clients should select a named profile and use supported mutations, rather than replacing the full runtime profile wholesale.

pub enum PermissionProfileSelectionParams {
    Profile {
        id: String,
        modifications: Option<Vec<PermissionProfileModificationParams>>,
    },
}

pub enum PermissionProfileModificationParams {
    AdditionalWritableRoot { path: AbsolutePathBuf },
}

This keeps UI state explainable and avoids having clients invent permission shapes that cannot be tied back to a known profile.

What changed

Protocol and Runtime Model

  • Added ActivePermissionProfile { id, extends, modifications } to the protocol model.
  • Added request-side PermissionProfileSelectionParams for selecting a named built-in or user-defined profile plus supported bounded modifications.
  • Kept PermissionProfile as the exact runtime permissions payload. ActivePermissionProfile is metadata only and is cleared when it can no longer faithfully describe the effective permissions.

Config and Session State

  • Thread/session configuration now tracks the active profile when permissions come from default_permissions or the implicit built-in default, including :workspace, :read-only, :danger-no-sandbox, and user-defined [permissions.<id>] profiles.
  • Active profile metadata is cleared if managed requirements force the selected profile to fall back, so clients do not show a profile name for permissions that are no longer the selected profile's effective permissions.
  • Active profile metadata is omitted for implicit :workspace profiles that depend on legacy [sandbox_workspace_write] customizations. Explicitly selecting :workspace through the new API intentionally ignores those legacy roots/network/tmp settings, so omitting metadata preserves behavior by making clients fall back to the legacy sandbox projection.
  • Bounded additional writable roots now apply to managed profiles such as :read-only instead of being treated as a legacy workspace-only knob. The implicit :workspace path still preserves legacy workspace metadata carveout behavior.

App-Server API

  • App-server thread/start, thread/resume, thread/fork, and turn/start request params now use experimental permissions: PermissionProfileSelectionParams instead of experimental raw permissionProfile overrides.
  • App-server thread responses continue to expose experimental permissionProfile as the exact active runtime permissions and now expose experimental activePermissionProfile as the display/provenance metadata.
  • The legacy response sandbox field remains the compatibility projection.
  • Explicit turn/start permission selections are rejected before the turn starts if requirements force the selected profile to fall back, preserving the previous request-time validation behavior.

TUI and Exec Clients

  • TUI and exec app-server clients send named permission selections when active profile metadata is known.
  • When config still comes from legacy sandbox_mode/-s, exec continues to send the legacy sandbox projection so remote app-server sessions preserve the requested mode.
  • TUI turn/start uses active-profile selection for embedded app-server sessions when available, but falls back to the legacy sandbox projection when no active profile id exists so local permission-preset changes are not dropped.
  • TUI turn routing only sends active-profile selections when the queued permission profile still matches current config, avoiding stale profile metadata if dispatch races a local config change.
  • TUI session caching and status rendering preserve the active profile name so the status card can render built-ins as Read Only, Workspace, or No Sandbox/Full Access, while user-defined profiles render as Profile <id> and Auto-review shows as Workspace (auto-review).
  • /status no longer labels active built-in profiles as Custom, uses workspace in user-facing permissions text instead of the legacy workspace-write label, distinguishes Auto-review from manual on-request review, and shows bounded modifications such as + 1 writable root when present.

Schema and Docs

  • Updated generated app-server schema fixtures for the request/response shape changes.
  • Updated app-server README examples to show permissions selections and activePermissionProfile responses.

Review guide

  • Start with codex-rs/protocol/src/models.rs and codex-rs/app-server-protocol/src/protocol/v2.rs for the shape of ActivePermissionProfile and PermissionProfileSelectionParams.
  • Then review codex-rs/core/src/config/mod.rs and codex-rs/core/src/session/session.rs for the key invariant: runtime enforcement still comes from PermissionProfile, while active-profile metadata is only kept when the selected profile remains faithful after constraints and can be re-selected without losing legacy config semantics.
  • Finally review codex-rs/app-server/src/codex_message_processor.rs, codex-rs/tui/src/app_server_session.rs, codex-rs/exec/src/lib.rs, and codex-rs/tui/src/status/card.rs for the API plumbing and UI display behavior.

Verification

  • Config/profile invariants: targeted codex-core tests cover active metadata for built-ins, user-defined profiles, additional writable roots, legacy workspace settings, requirement-forced fallback, and operation-kind routing.
  • App-server protocol/API: cargo test -p codex-app-server-protocol, targeted codex-app-server tests for invalid turn permission selection, and generated schema fixture updates.
  • TUI/exec clients: targeted codex-tui and codex-exec tests cover embedded named-profile selection, remote legacy fallback, no-active-profile fallback, session response hydration, and status labels including writable-root modifications.
  • UI snapshots: status snapshots were updated so reviewers can inspect the visible /status label changes, including Workspace (auto-review).
  • Lint/format: scoped just fix runs for the changed Rust crates plus just argument-comment-lint.

@bolinfest bolinfest requested a review from a team as a code owner April 28, 2026 23:03
@bolinfest bolinfest force-pushed the pr20095 branch 7 times, most recently from af42be1 to 46f2ed2 Compare April 29, 2026 01:11
@bolinfest bolinfest force-pushed the pr20095 branch 3 times, most recently from bd7080b to e2ae150 Compare April 29, 2026 02:59
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: e2ae150346

ℹ️ 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 on lines +1408 to +1410
ThreadParamsMode::Remote => {
PermissionProfile::from_legacy_sandbox_policy_for_cwd(&sandbox.to_core(), cwd)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Preserve full permission profile for remote bootstrap

For ThreadParamsMode::Remote, this maps session permissions from the legacy sandbox field only, which is lossy for profiles that include restricted read rules. The protocol itself documents that restricted-read variants are no longer representable in sandbox and require permissionProfile (see app-server-protocol/src/protocol/v2.rs around the readOnly.access / workspaceWrite.readOnlyAccess rejection paths), so threads started/resumed/forked against a remote server can now be widened in the TUI state to generic read-only/workspace-write permissions instead of the actual constrained profile.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

@bolinfest bolinfest Apr 29, 2026

Choose a reason for hiding this comment

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

[codex] Addressed in 8cae849. I restored the experimental response-side permissionProfile on thread/start, thread/resume, and thread/fork as the exact runtime permissions payload, kept activePermissionProfile as provenance/display metadata, and updated bootstrap mapping to prefer response permissionProfile with legacy/local fallbacks only for older or missing app-server responses. Added TUI and exec coverage for preserving the response profile instead of reconstructing it from sandbox or local config.

@bolinfest bolinfest force-pushed the pr20095 branch 17 times, most recently from 138eef5 to 80d8db2 Compare April 29, 2026 10:06
Copy link
Copy Markdown
Contributor

@fcoury-oai fcoury-oai left a comment

Choose a reason for hiding this comment

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

Walked through the story of the PR in the suggested order. Code looks good.

I only have a minor concern from the TUI user's perspective, which may be either me being naïve or something we already plan on addressing at a later time:

When I first run /status I see permissions as Workspace (on-request), which is somewhat foreign to my current understanding (as a user) of how permissions work.

Then I use /permissions to change my permissions to, let's say, "Full Access". When I do /status again, I see Permissions: Full Access which is reassuring.

Next I change the permissions to Auto-review and on /status I now see Workspace (on-request) again, which leads me to believe something is wrong, because that was the Default permissions when I started the session.

Not sure if this makes sense, since I don't have the end-goal in mind but I thought it was worth sharing.

I am approving the PR since the code looks good and the guided walkthrough also made me make sense of the changes.

@bolinfest bolinfest force-pushed the pr20095 branch 2 times, most recently from 9f1ad76 to 55470a4 Compare April 29, 2026 22:41
@bolinfest
Copy link
Copy Markdown
Collaborator Author

@fcoury-oai thanks for calling this out. I agreed the underlying state was correct but the /status label was ambiguous: Auto-review intentionally uses the Workspace permission profile with on-request approvals routed through the auto-reviewer. I updated the status formatter so that state now renders as Workspace (auto-review) rather than Workspace (on-request), and added both a focused assertion and a /status snapshot covering the flow.

@bolinfest bolinfest merged commit ac4332c into main Apr 30, 2026
48 of 61 checks passed
@bolinfest bolinfest deleted the pr20095 branch April 30, 2026 03:55
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants