Skip to content

clients: send permission profiles to app-server#18280

Merged
bolinfest merged 1 commit intomainfrom
pr18280
Apr 22, 2026
Merged

clients: send permission profiles to app-server#18280
bolinfest merged 1 commit intomainfrom
pr18280

Conversation

@bolinfest
Copy link
Copy Markdown
Collaborator

@bolinfest bolinfest commented Apr 17, 2026

Why

After app-server can accept PermissionProfile, first-party clients should stop preferring legacy sandbox fields when canonical permission information is available. This keeps the migration moving without removing legacy compatibility yet.

The client side still has mixed surfaces during the stack: embedded thread start/resume/fork and exec initial turns can derive a profile directly from local config, while TUI remote sessions and some turn-start paths only have a legacy/server-context-safe sandbox projection. Those paths keep sending legacy sandbox fields rather than synthesizing or sending lossy/local-only profiles.

What changed

  • Sends permissionProfile from exec and embedded TUI thread start/resume/fork requests when config has a representable profile.
  • Keeps legacy sandbox fallback for external sandbox policies, TUI remote thread lifecycle requests, and TUI turn-start requests that do not yet carry the active profile.
  • Sends the actual config-derived permissionProfile for exec initial turns instead of rebuilding one from the legacy sandbox projection.
  • Stores response permissionProfile as optional in TUI session state so external sandbox responses and compatibility payloads preserve null.
  • Updates tests for request construction and response mapping.

Verification

  • cargo check --tests -p codex-tui -p codex-exec
  • cargo test -p codex-tui app_server_session -- --nocapture
  • cargo test -p codex-exec thread_start_params -- --nocapture
  • cargo test -p codex-tui app_server_session::tests::thread_lifecycle_params -- --nocapture
  • just fix -p codex-tui -p codex-exec
  • just fix -p codex-tui

Stack created with Sapling. Best reviewed with ReviewStack.

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: ad29119e25

ℹ️ 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/tui/src/app_server_session.rs Outdated
bolinfest added a commit that referenced this pull request Apr 22, 2026
## Why

#18275 anchors session-scoped `:cwd` and `:project_roots` grants to the
request cwd before recording them for reuse. Relative deny glob entries
need the same treatment. Without anchoring, a stored session permission
can keep a pattern such as `**/*.env` relative, then reinterpret that
deny against a later turn cwd. That makes the persisted profile depend
on the cwd at reuse time instead of the cwd that was reviewed and
approved.

## What changed

`intersect_permission_profiles` now materializes retained
`FileSystemPath::GlobPattern` entries against the request cwd, matching
the existing materialization for cwd-sensitive special paths.

Materialized accepted grants are now deduplicated before deny retention
runs. This keeps the sticky-grant preapproval shape stable when a
repeated request is merged with the stored grant and both `:cwd = write`
and the materialized absolute cwd write are present.

The preapproval check compares against the same materialized form, so a
later request for the same cwd-relative deny glob still matches the
stored anchored grant instead of re-prompting or rejecting.

Tests cover both the storage path and the preapproval path: a
session-scoped `:cwd = write` grant with `**/*.env = none` is stored
with both the cwd write and deny glob anchored to the original request
cwd, cannot be reused from a later cwd, and remains preapproved when
re-requested from the original cwd after merging with the stored grant.

## Verification

- `cargo test -p codex-sandboxing policy_transforms`
- `cargo test -p codex-core --lib
relative_deny_glob_grants_remain_preapproved_after_materialization`
- `cargo clippy -p codex-sandboxing --tests -- -D
clippy::redundant_clone`
- `cargo clippy -p codex-core --lib -- -D clippy::redundant_clone`

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18867).
* #18288
* #18287
* #18286
* #18285
* #18284
* #18283
* #18282
* #18281
* #18280
* #18279
* #18278
* #18277
* #18276
* __->__ #18867
@bolinfest bolinfest force-pushed the pr18279 branch 2 times, most recently from 2b145dd to 9048348 Compare April 22, 2026 03:23
bolinfest added a commit that referenced this pull request Apr 22, 2026
## Why

`Permissions` should not store a separate `PermissionProfile` that can
drift from the constrained `SandboxPolicy` and network settings. The
active profile needs to be derived from the same constrained values that
already honor `requirements.toml`.

## What changed

This adds derivation of the active `PermissionProfile` from the
constrained runtime permission settings and exposes that derived value
through config snapshots and thread state. The app-server can then
report the active profile without introducing a second source of truth.

## Verification

- `cargo test -p codex-core --test all permissions_messages --
--nocapture`
- `cargo test -p codex-core --test all request_permissions --
--nocapture`



























---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18277).
* #18288
* #18287
* #18286
* #18285
* #18284
* #18283
* #18282
* #18281
* #18280
* #18279
* #18278
* __->__ #18277
@bolinfest bolinfest requested a review from a team as a code owner April 22, 2026 06:34
@bolinfest bolinfest force-pushed the pr18279 branch 3 times, most recently from 89a0761 to d29fd8d Compare April 22, 2026 07:09
@bolinfest bolinfest force-pushed the pr18280 branch 2 times, most recently from 4d8aa32 to b9e3d68 Compare April 22, 2026 15:40
@bolinfest bolinfest force-pushed the pr18279 branch 2 times, most recently from 99901f5 to 5887c23 Compare April 22, 2026 19:27
// not synthesize a lossy profile until the active profile
// is plumbed through user turns.
sandbox_policy: Some(sandbox_policy.into()),
permission_profile: None,
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.

This TUI turn/start path still sends sandboxPolicy with permissionProfile: None. After this PR, embedded thread start/resume/fork installs the full permissionProfile, but every ordinary prompt is an override, so app-server applies this legacy sandbox update before running the turn. SessionConfiguration::apply rebuilds the filesystem policy from SandboxPolicy and only preserves deny entries/depth, which discards any active profile state that is not represented by the legacy projection. Plumb the active ThreadSessionState.permission_profile into turn_start like the exec path does, or omit the sandbox override when there is no per-turn permission change.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants