fix(server): Codex keeps your permissions profile after Full access and back to Auto#23631

open6
main
fix/issue-20266
opened2d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 2d ago

Problem

With a Codex default_permissions profile that denies reads of **/.env and **/.env*, one Full access turn silently removed those denies for the rest of the thread. A fresh Auto turn had them. After Auto β†’ Full access β†’ Auto, the next Auto turn ran under a plain workspace-write sandbox: the injected permission instructions and the profile Codex enforces both lacked the deny entries, even though the composer showed Auto again.

The cause: T3 sends a legacy sandboxPolicy on every turn/start. Codex keeps a named profile only while that policy matches it. After dangerFullAccess and back to workspaceWrite, Codex builds a legacy profile without the profile's deny entries.

Change

  • Remember the profile. CodexAdapterV2 records the user-defined permission profile per Codex thread, as thread/start, cold thread/resume, and thread/fork report it in activePermissionProfile. Built-in :-prefixed ids are ignored. A resume of an already-loaded thread reports its current state (no profile after a legacy-sandbox turn), so it does not clear the remembered one.
  • Select it by id. When a turn's runtime mode maps to the same sandbox type as that profile, and no explicit sandbox override applies, turn/start selects the profile with Codex's permissions: <id> instead of sandboxPolicy. Other modes, users without a named profile, and Codex versions that do not report the profile send exactly what they did before.
  • Fall back if the profile disappears. If the profile was removed or renamed in config.toml while the thread stays loaded, Codex rejects the permissions turn with a default_permissions error. T3 then forgets the profile and retries once with the legacy sandboxPolicy.
  • Wire plumbing. permissions is added to the turn/start encoder in effect-codex-app-server. thread/start and thread/fork are read raw because the generated response schemas strip activePermissionProfile.

Scope and approval

Fixes #20266

Triaged bug via the automated issue flow.

Verification

  • vp test run src/orchestration-v2/Adapters/CodexAdapterV2.test.ts: 82 passed. New tests:
    • The Auto β†’ Full access β†’ Auto round trip, with the thread/resume calls T3 makes between mode changes. It fails on main: the Auto turn/start carries sandboxPolicy: workspaceWrite and no permissions.
    • The stale-profile fallback.
    • Mode mismatch: approval-required and full-access keep sandboxPolicy.
    • Built-in ids are not remembered.
  • packages/effect-codex-app-server vp test run src/client.test.ts: 2 passed. tsc --noEmit -p apps/server: 0 errors.
  • Real app: web dev client on Linux x86_64 with Codex CLI 0.163.0 and the reporter's profile. I followed the reporter's steps in one new thread and read each turn's injected permission block and enforced permission_profile from Codex's rollout file. No protected file was read.
RunTurn 3 (Auto after Full access)
Before, origin/main ed3f965e07no .env denies in the permission block or the enforced profile
After, this branch<cwd>/**/.env and <cwd>/**/.env* in both
After: Full access, server restart, then Autoboth denies present
After: profile renamed mid-thread, then Autopermissions turn rejected, legacy retry completes the turn

Runtime transcript: 20266-runtime-transcript.txt

Not checked: macOS, the reporter's platform. The change is in the app-server protocol requests and has no platform-specific code.

Claude Opus 5.5 via Claude Code in T3 Code (implementation and tests on claude-opus-5-5; reviews on claude-opus-5-5 and claude-sonnet-5-5).

πŸ€– Generated with Claude Code

Checks

All checks passed

Added 6 commits

  • fix(server): Codex keeps your permissions profile when T3 resumes the thread between turns (#20266)41f688b
  • fix(server): only fall back from a Codex permissions profile on a profile config error (#20266)862dab6
  • Merge remote-tracking branch 'origin/main' into fix/issue-20266a9ae44f
  • fix(server): Codex turns survive a renamed permissions profile, and forks keep it (#20266)c85575c
  • fix(server): Codex keeps your permissions profile after switching to Full access and back (#20266)deff3b8
  • test(server): reproduce Codex permissions profile loss after a Full access round trip (#20266)f650044
Synced from GitHub

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval β€” This production change alters Codex sandbox and permission-profile selection, including restoration of deny-read protections for sensitive files after runtime-mode changes. The scope is focused and well tested, but the security-sensitive permission behavior warrants human review.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Review in Change Stack β†’

No actionable comments were generated in the recent review. πŸŽ‰

ℹ️ Recent review info
βš™οΈ Run configuration
πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between f054d1b8018721bd997d99cbb918a7b17bf098a4 and a9ae44f56c0947fd069ea3f741351e6431c18d98.

πŸ“’ Files selected for processing: 3
  • apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
  • apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
  • packages/effect-codex-app-server/src/client.ts

Limit details: You’ve used all 10 included reviews currently available.

πŸ“ Walkthrough

The Codex adapter now retains named permission profiles from thread responses and uses a matching profile for turns when no explicit sandbox policy applies. If Codex rejects the profile as unavailable, the adapter retries with the legacy sandbox policy.

Changes: Codex permission profile handling

Layer / File(s)Summary
Decode and select permission profiles
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts, packages/effect-codex-app-server/src/client.ts, apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
The turn-start schema accepts permissions. The adapter decodes named profiles and uses a profile only when its sandbox type matches the runtime mode and no explicit sandbox policy applies. Tests cover explicit policies, mismatched profiles, and built-in profiles.
Capture profiles across thread operations
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
The adapter stores reported profiles by thread. It reads raw thread start, resume, and fork responses to capture profile data.
Apply profiles and retry unavailable profiles
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
Turn-start requests use the stored profile. When Codex rejects it with an error mentioning default_permissions, the adapter forgets the profile and retries with the legacy sandbox policy. Replay tests cover the Auto β†’ Full access β†’ Auto sequence and the retry path.

Priority: βž– Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix Β· Severity of issue fixed: Medium

Suggested reviewers: t3dotgg

Merge Risk: βšͺ Minimal Β· up to a9ae4

No actionable merge-blocking issue is established. The profile fallback can proceed with normal validation.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title is concise, conventional, and accurately describes the main fix: preserving the Codex permissions profile after switching from Full access back to Auto.
Description checkPassedThe description includes all required sections. It clearly explains the problem, implementation, scope approval through issue #20266, focused verification results, manual testing, and known platform l…
Linked Issues checkPassedIssue #20266 requires the configured .env and .env* read-deny entries to return after Auto β†’ Full access β†’ Auto. The PR remembers non-built-in profiles per Codex thread, preserves the profile acro…
Out of Scope Changes checkPassedThe changed adapter logic, replay tests, raw response handling, and turn/start protocol plumbing support issue #20266. The tests validate profile retention and fallback behavior. No unrelated produc…
Full details: Description check

Explanation

The description includes all required sections. It clearly explains the problem, implementation, scope approval through issue #20266, focused verification results, manual testing, and known platform limitations.

Full details: Linked Issues check

Explanation

Issue #20266 requires the configured .env and .env* read-deny entries to return after Auto β†’ Full access β†’ Auto. The PR remembers non-built-in profiles per Codex thread, preserves the profile across thread resume, and sends permissions: &lt;id&gt; when the runtime sandbox matches. The stale-profile retry preserves operation when Codex removes or renames the profile. The adapter tests cover the round trip, resume calls, stale-profile fallback, mode mismatch, explicit sandbox overrides, and built-in profiles. The app-server client encodes permissions for turn/start.

Full details: Out of Scope Changes check

Explanation

The changed adapter logic, replay tests, raw response handling, and turn/start protocol plumbing support issue #20266. The tests validate profile retention and fallback behavior. No unrelated product behavior is established by the supplied change summary or the inspected files.

✨ Finishing Touches
πŸ“ Generate docstrings
  • Commit to this branch
  • Create a new PR
πŸ§ͺ Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix Β· Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Usage-based review receipt

Note

This review was completed with usage-based billing: files reviewed beyond your plan's included limits are billed at $0.25/file. View usage-based billing.

Comment @coderabbitai help to get the list of available commands.

Synced from GitHub

Thread transfer impact

βœ… Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire4.9 KiB5.0 KiB+23 B (+0.5%)6.8 KiBβœ…
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiBβœ…
CodexLive turn WebSocket wire1.2 KiB1.2 KiB+23 B (+1.9%)2.0 KiBβœ…
CodexLive turn WebSocket decoded20.8 KiB20.9 KiB+41 B (+0.2%)29.3 KiBβœ…
CodexLive turn messages12+1 (+100.0%)8βœ…
ClaudeTotal thread wire5.0 KiB5.0 KiBβˆ’7 B (βˆ’0.1%)6.8 KiBβœ…
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiBβœ…
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiBβˆ’7 B (βˆ’0.6%)2.0 KiBβœ…
ClaudeLive turn WebSocket decoded21.2 KiB21.2 KiB0 B (0.0%)29.3 KiBβœ…
ClaudeLive turn messages220 (0.0%)8βœ…

Baseline: f054d1b Β· PR result: a9ae44f Β· Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.