[Windows app] Full Access -> Custom leaves stale approvals_reviewer=user and bypasses Auto-review
What version of the Codex App are you using (From “About Codex” dialog)?
Version 26.810.52044
The same state-transition pattern also reproduced on this same computer in a separate task on July 19, 2026, while running the then-current Codex Desktop version.
What subscription do you have?
Not included in this public report; the issue appears to be local Desktop permission-state handling rather than subscription-specific behavior.
What platform is your computer?
Microsoft Windows NT 10.0.26200.0 x64
What issue are you seeing?
Switching a running Codex Desktop task from Custom (config.toml) to Full Access, then back to Custom, can restore the sandbox and approval policy but leave the approval reviewer from Full Access.
My user-level config is:
approval_policy = "on-request"
approvals_reviewer = "auto_review"
sandbox_mode = "workspace-write"
The expected Custom state is therefore:
approval_policy = on-request
approvals_reviewer = auto_review
sandbox_policy = workspace-write
However, the affected task transitions as follows:
1. Custom
approval_policy = on-request
approvals_reviewer = auto_review
sandbox_policy = workspace-write
2. Switch to Full Access
approval_policy = never
approvals_reviewer = user
sandbox_policy = danger-full-access
3. Switch back to Custom
approval_policy = on-request
approvals_reviewer = user <-- stale / incorrect
sandbox_policy = workspace-write
At step 3, approval requests are routed directly to the human UI. Auto-review never receives them, even though config.toml still says approvals_reviewer = "auto_review".
This produces the user-visible symptom of manual approval dialogs appearing for a task that is expected to be using Auto-review.
A fresh task at the same time correctly uses:
approval_policy = on-request
approvals_reviewer = auto_review
sandbox_policy = workspace-write
That rules out a malformed global config and points to stale per-thread/task permission state.
This has now reproduced in two separate tasks at different times. The earlier reproduction on July 19 showed the same transition:
on-request + auto_review
-> Full Access: never + user
-> back to managed/Custom: on-request + user
The later reproduction showed the same reviewer leak again after returning from Full Access.
What steps can reproduce the bug?
- On Windows Codex Desktop, configure:
``toml``
approval_policy = "on-request"
approvals_reviewer = "auto_review"
sandbox_mode = "workspace-write"
- Start a fresh task in Custom (config.toml).
- Verify the effective task settings are:
``text``
approval_policy = on-request
approvals_reviewer = auto_review
sandbox_policy = workspace-write
- Switch the same running task to Full Access.
- Verify the effective task settings become approximately:
``text``
approval_policy = never
approvals_reviewer = user
sandbox_policy = danger-full-access
- Switch the same task back to Custom (config.toml).
- Trigger any operation that requires an escalation/approval under the managed sandbox.
- Inspect the effective task settings and approval routing.
Observed:
approval_policy = on-request
approvals_reviewer = user
sandbox_policy = workspace-write
The approval is shown to the user manually.
Expected:
approval_policy = on-request
approvals_reviewer = auto_review
sandbox_policy = workspace-write
The request should be routed to the Auto-review reviewer agent.
What is the expected behavior?
Switching permission modes should replace the complete effective permission context atomically.
When switching Full Access -> Custom (config.toml), Codex Desktop should restore all relevant fields from the configured Custom state, including approvals_reviewer.
A field from Full Access (approvals_reviewer = user) must not remain attached to the task after the sandbox and approval policy have already returned to the Custom values.
Additional information
This appears related to #32338, which reports broader permission-profile desynchronization and field mixing on Windows, but this report is intentionally narrower: it describes a repeatable Full Access -> Custom reviewer-state leak with a concrete three-state transition and a specific user-visible consequence: Auto-review is bypassed and manual approvals appear.
It also differs from reports where Auto-review rejects or fails an approval. In this case Auto-review is not involved at all: the task's effective approvals_reviewer has changed to user.
Manually selecting Approve for me restores approvals_reviewer = auto_review, and a new task also reads the global config correctly. This further suggests the bug is in per-thread/task permission-state restoration or field-wise merging during the Full Access -> Custom transition, rather than config parsing.
1 Comment
The "stale per-thread permission state" hypothesis is verifiable in this repo's shared app-server/TUI code, and the mechanism explains all three of your observations (reviewer leaks on the round-trip, fresh tasks are correct, and the leak survives restarts of the same task). Traced on
main@ 1f41cc5d92.1. Thread-settings overrides have no way to say "revert to config" —
Nonemeans "keep"When a client switches a running thread's permissions, it goes through
thread/settings/update→build_thread_settings_overrides:https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/app-server/src/request_processors/turn_processor.rs#L685-L825
Every field is an
Option, and an absent field means "leave the thread's current value alone". Switching to Full Access explicitly setsapprovals_reviewer = useron the thread. Switching back to a config-derived Custom profile sends thepermissionsselection — and crucially, whenpermissionsis applied, the server callsload_for_cwd(which loads your full config, includingapprovals_reviewer = "auto_review") but extracts only the permission-profile fields from it (permission_profile,active_permission_profile,profile_workspace_roots); the loaded config's reviewer is discarded, andapprovals_reviewerin the resulting overrides comes solely from the request params. So unless the client re-sends the reviewer explicitly, the thread keepsuserfrom the Full Access step, while sandbox/policy get restored. There is currently no tri-state to express "clear this override and fall back to config".2. The TUI exhibits the same leak, which localizes the bug to this shared layer
Selecting a custom permission profile in the TUI builds the selection with
approval_policy: None, approvals_reviewer: None:https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/tui/src/chatwidget/permissions_menu.rs#L188-L201
and
apply_permission_profile_selectiononly touches the reviewerif let Some(reviewer)(config_persistence.rs#L179, #L203) — so after a Full Access round-trip, both the in-memory config and theOverrideTurnContextsent to the thread retainuser, even thoughconfig.tomlstill saysauto_review. Full Access, by contrast, always forcesApprovalsReviewer::Useron its accept path (permission_popups.rs#L258-L283). The asymmetry — Full Access sets the reviewer explicitly, Custom restores it as "no change" — is the bug.3. Why it survives restarts of the affected task but not fresh tasks
On resume, the server backfills the reviewer from the last persisted
TurnContext/ThreadSettingsAppliedentry in the rollout history, overriding config:https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/app-server/src/request_processors/thread_processor.rs#L186-L203
Since the Custom switch never wrote a corrected reviewer into the thread's turn context (see 1), the last persisted value is still
user, and every resume of that thread re-applies it. A fresh task has no history, readsconfig.toml, and correctly getsauto_review— exactly what you observed.High-level fix outline
permissionsselection is applied inbuild_thread_settings_overrides, deriveapprovals_reviewer(and arguablyapproval_policy) from the config it already loads viaload_for_cwd, unless the same request overrides them explicitly. This fixes desktop, TUI, and any other client in one place, and also writes the corrected reviewer into the turn context so the resume path (3) heals itself.ThreadSettingsOverrideswith an explicit "reset to config" variant so clients can clear a stale override —Option::Nonecurrently conflates "no change" with "no opinion".permission_profile_selection_itemcould populate the reviewer from the effective config instead ofNone, mirroring what the built-in preset items already do.A regression test for the exact transition (
auto_review→ Full Access → custom profile → assert reviewer restored, then resume the thread and assert again) would cover both the live-switch and the resume leak.