# fix\(server\): Codex keeps your permissions profile after Full access and back to Auto · PR \#23631 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23631)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: open

Source repository: pingdotgg/t3code

Source branch: fix/issue-20266

Target branch: main

Head commit: a9ae44f56c0947fd069ea3f741351e6431c18d98

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/23631)

[Commits](https://git.cafe/pingdotgg/t3code/pull/23631/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/23631/diffs?format=markdown)

## Description

---

## 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.

| Run | Turn 3 (Auto after Full access) |
| --- | --- |
| Before, `origin/main` ed3f965e07 | no `.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 Auto | both denies present |
| After: profile renamed mid-thread, then Auto | `permissions` turn rejected, legacy retry completes the turn |

Runtime transcript: [20266-runtime-transcript.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7b8bec4fdc1ed1a2/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](https://claude.com/claude-code)

