Repository navigation
Permission / approval mode selection does not persist for new or existing threads #29915
Description
Summary
Permission / approval mode selection is not reliably persisted for either new threads or already-loaded existing threads.
This originally looked like only a loaded-thread thread/resume problem, but there is a broader desktop-visible failure:
- On an existing loaded thread, changing approval / permission mode through resume appears to work in the UI, but the live thread keeps the old effective settings.
- On a new thread, the permission picker allows selecting non-workspace options such as Full access, but after the thread starts / first prompt executes, the effective state falls back to workspace.
- The dropdown state can therefore look selectable before submission while the running thread state does not reflect the selected mode.
New-thread repro
- Open Codex desktop.
- Start a new thread.
- In the permission / approval picker, choose a non-workspace mode, for example Full access.
- Send the first prompt / start the thread.
- Reopen or inspect the permission picker.
Observed: the running thread shows workspace selected again instead of the selected mode.
Expected: the selected permission profile should be sent to app-server, reflected in the thread/start response, and used as the thread's effective permission state. If policy or requirements disallow the requested mode, the UI should show that explicitly rather than appearing to accept it and silently reverting.
Existing loaded-thread repro
- Start a thread with default / read-only-ish settings.
- Materialize the rollout with a simple turn.
- Resume or switch back to the already-loaded thread while requesting different approval / permission settings, for example
approvalPolicypluspermissions = ":danger-full-access". - Inspect the resume response or subsequent effective settings.
Observed: loaded-thread thread/resume previously ignored supported overrides and kept the old effective permission mode.
Expected: supported resume overrides should be validated and applied to the live thread, and the response should reflect the effective settings.
Suggested fix candidates
Two linked commits on a fork cover the two parts found so far:
-
Loaded-thread backend fix:
andrewsohrabi@94e81eaThis adds a regression test for loaded
thread/resumeapplyingapprovalPolicy+permissions, then routes supported loaded-resume overrides through the same validated thread-settings path used bythread/settings/update. Startup-only fields such asconfig,baseInstructions, anddeveloperInstructionsremain ignored while the thread is already loaded. -
New-thread schema / client-contract fix:
andrewsohrabi@d39d601This adds coverage proving stable generated TypeScript includes permission-profile selection for thread lifecycle requests, then exposes
permissionsplusactivePermissionProfileforthread/start,thread/resume, andthread/forkin the stable protocol fixtures. Without this, generated stable clients can list/read permission profiles but cannot cleanly send the selected named profile or read profile provenance from lifecycle responses.
Compare:
main...andrewsohrabi:codex/permissions-fix
Validation run locally
Focused checks passed:
just test -p codex-app-server-protocol generated_ts_optional_nullable_fields_only_in_paramsjust test -p codex-app-server-protocoljust test -p codex-app-server thread_start_applies_named_permission_profile thread_resume_loaded_thread_applies_permission_overrides
Full just test -p codex-app-server was attempted. In this managed sandbox it ended with unrelated local environment failures: nested macOS sandbox-exec returns exit 71 (sandbox_apply: Operation not permitted), one executor MCP test cannot locate target/debug/codex, and a few file-watch / sandboxed image or file-change tests time out. The permission regressions above passed inside that run.
just fix -p ... and just fmt were also attempted but are blocked by the local toolchain / sandbox (cargo clippy and cargo fmt missing, dotslash missing, and uv cannot initialize its cache). git diff --check passes.
Activity
github-actions commentedon Jun 24, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
andrewsohrabi commentedon Jun 24, 2026
Not a duplicate
andrewsohrabi commentedon Jun 24, 2026
Correction:
@codexmentions on GitHub issues do not trigger the Codex review workflow. Codex review requests are PR-based, so the actionable fix links are in the suggested-fix comment below.andrewsohrabi commentedon Jun 25, 2026
Suggested fix candidates:
The first commit adds a regression test for loaded
thread/resumeapplyingapprovalPolicy+permissions, then routes supported loaded-resume overrides through the same validated thread-settings path used bythread/settings/update. Startup-only fields such asconfig,baseInstructions, anddeveloperInstructionsremain ignored while the thread is already loaded.The second commit covers the new-thread symptom: stable generated protocol fixtures now expose
permissionsandactivePermissionProfileforthread/start,thread/resume, andthread/fork, so generated clients can send the selected named permission profile and read the effective profile provenance from lifecycle responses.Focused protocol and app-server regression tests pass locally. Full
codex-app-serverwas attempted, but this managed sandbox hits unrelated local failures around nested macOSsandbox-exec, a missingtarget/debug/codexhelper binary, and a few file-watch / sandboxed file-read timeouts.[-]Permission / approval mode does not persist when changed on existing threads[/-][+]Permission / approval mode selection does not persist for new or existing threads[/+]Expose permission profile selection in thread lifecycle schema
metrovoc commentedon Aug 3, 2026
Additional macOS reproduction on the current Codex Desktop build, linked to a private feedback upload.
Feedback ID:
019fc598-b009-7150-a7a9-c83dacc379a6Environment:
26.727.51351, build61190.146.0-alpha.9.225.6.0, arm64Relevant configuration:
Observed in the same existing non-Goal thread without changing
config.toml:activePermissionProfile.id = "Auto"and the profile's network policy is enabled.activePermissionProfile = null,approvalPolicy = "on-request", andsandboxPolicy.type = "workspaceWrite". The UI then shows Approve for me.activePermissionProfile = nullagain.The persisted and per-turn state showed this transition repeatedly across consecutive turns. This appears to match the thread lifecycle / permission-profile provenance problem described here: Desktop alternates between a named permission-profile payload and a legacy approval/sandbox override.
Expected behavior: the selected named permission profile remains attached across subsequent turns until the user explicitly changes it.
The private feedback contains the same sanitized reproduction. Session logs and browser logs were not included.
BeautyToolsAU commentedon Sep 15, 2026
Additional Windows reproduction and phone/remote impact — 15 September 2026
The user reports that a newly created chat displays Full Access by default, but the permission only takes effect after switching to Require approval and then back to Full Access.
Reproduction reported by the user
Evidence independently observed in this task
Before the user corrected the permission selection:
sandbox_mode = workspace-write,approval_policy = never, and a managed/restricted filesystem profile.MCP tool call requires approval, but approval policy is never.helper_sandbox_lock_failedandSetNamedSecurityInfoW sandbox dir failed: 5.After the user reported fixing permissions:
sandbox_mode = danger-full-accessand an unrestricted filesystem profile. Approval policy remainednever.The UI selection and exact toggle sequence are user-reported; the before/after runtime states and operation results are directly observed. These establish a practical recovery, but do not identify the internal cause or prove the MCP and Windows helper errors share one cause.
Remote impact
The user reports there is no permission-mode control when starting a remote session from the phone. This makes the desktop toggle workaround unavailable remotely, leaving the task blocked until the user returns to the desktop.
Expected behaviour
The displayed selection should match the effective task permissions and be applied at chat creation. If another policy prevents the selected mode, the UI should identify it clearly. The phone interface should expose effective permissions and a supported way to change them where allowed, or clearly explain a desktop-only requirement.
Environment: Windows desktop with phone/remote use; exact app builds and phone OS were not captured. Only sanitised technical observations are included; no account identifiers, local paths, customer/business data, screenshots, or conversation logs are attached.
edwardswhiskeyblues-alt commentedon Oct 6, 2026
Additional Windows reproduction — two existing threads silently changed from Full Access to Approve for me
I can reproduce the permission-persistence problem on the current Windows desktop build, with two independent existing local threads. This adds a multi-thread, timestamped reproduction where the effective runtime policy changed, not only the picker label.
Environment
26.928.31416263000.149.0-alpha.4.1and0.159.2default_permissions = ":danger-full-access"; that file had not changed during either transition.Observed timeline (UTC, 2026-10-05 / 2026-10-06)
danger-full-access+approval_policy=never). At15:10:53Z, its next submitted turn instead carriedworkspace-write+approval_policy=on-request+approvals_reviewer=auto_review(the “Approve for me” preset). There was no deliberate permission action by the user.20:05:13Z. Its rollout recordedthread_settings_appliedwith the managed/workspace profile immediately before task start/compaction, again without a standalone user-authored settings change.04:03:31Zand thread A at04:06:00Z. Subsequent state/turn context correctly showedactive_permission_profile=:danger-full-access,sandbox_policy=danger-full-access, andapproval_policy=neverin both threads.This rules out a purely visual selector issue: the sandbox, approval policy, reviewer, and active profile all changed in the actual turn/runtime context. It also occurred in two different long-lived threads several hours apart, which suggests a persisted-state or rehydration path rather than a single accidental click.
Workflow impact and recovery trap
One affected orchestration thread stopped at a cross-thread MCP send. Restoring Full Access correctly restored unrestricted local execution, but the side-effecting MCP call then failed before delivery with:
MCP tool call requires approval, but approval policy is neverThat second behavior is consistent with the separate Full Access / prompt-conflict class tracked in #41068: the MCP tool requires an interactive approval, while Full Access selects
approval_policy=never. It is not evidence that the manual Full Access restore failed; it is a downstream trap that makes recovery from the silent downgrade confusing. Switching to anon-requestprofile allows the approval path to exist, but changes the user’s intended trust boundary again.New UI/runtime desynchronization during manual recovery
After the finding above, the user opened the same existing thread and tried to change Full Access to Ask for approval. The option remained available in the menu, but the UI appeared not to execute or retain the selection. The local records show the opposite:
04:23:38Z,04:23:52Z, and04:23:55Z: three separatethread_settings_appliedevents were emitted.approval_policy=on-request,approvals_reviewer=user,active_permission_profile=:workspace, and the managed workspace filesystem policy.approval_mode=on-requestwith the managed sandbox policy.So the backend accepted the selection three times while the visible selector still appeared to remain on Full Access. This creates a dangerous trust-boundary mismatch: the user cannot reliably tell whether the next turn will run unrestricted or inside the workspace sandbox. No new turn was started after these clicks, so the next turn's inherited context has not yet been tested.
Expected behavior
No project names, local paths, thread IDs, conversation content, account data, or external endpoint details are included here. The exact timestamps and permission fields above were taken from the local persisted state and rollout records.
StutteringJohnMelendez commentedon Oct 7, 2026
Could maintainers provide a status update on the permission-selection persistence problem documented here, including the supported contract for cloud-backed conversations?
This issue already distinguishes the displayed picker selection from effective thread settings and discusses
thread/resumeandthread/settings/update. Related public Windows reports #49477 and #50428 describe permission-profile/request-shape correlations with failed follow-ups. These reports do not establish that all symptoms have one cause.Please clarify:
thread/settings/update?This is a request for triage and documented behavior based only on the existing public reports. It does not assert a new unsupported-method reproduction or disclose any user-specific environment, timestamps, identifiers, settings records, or logs.
info618 commentedon Oct 10, 2026
On macOS, ChatGPT/Codex Desktop 26.1007.21159 (20052) continues dispatching four existing local sessions with workspace-write, on-request and approvals_reviewer=user after Approve for me was selected and the app restarted. Local configuration specifies an automatic reviewer. The configured updater reports up to date. Please provide a supported repair that preserves existing sessions and permission boundaries.
1 remaining item
virajchoudhary commentedon Oct 13, 2026
Verification follow-up to #29915 (comment).
The two coverage gaps identified there are now closed in the local candidate. The existing regression starts with
approvalsReviewer=autoReview, changes it touser, and directly asserts the accepted reviewer in the resume response, settings notification, fresh-process restored response, and subsequent turn context. It shuts down the test app-server immediately after the acknowledged update and restores the same thread in a fresh process before any subsequent turn. Legacy sandbox parity remains covered afterward.Both strengthened cases, legacy and paginated history, pass under pinned Rust 1.95 with locked dependencies and serial execution. A fresh independent read-only source review confirms the sequencing and assertions close both gaps without a false assertion. Production implementation is unchanged; the earlier surrounding-suite results remain applicable. The updated patch still covers 72 distinct integration cases overall, not two newly added cases.
Fork publication remains blocked. Retrying the ordinary authorized
git commitafter this test-only revision was again refused before execution withapproval required by policy, but AskForApproval is set to Never. No commit, push, upstream PR, installed-app modification, provider change, or permission-config change occurred. These source-level tests still do not certify the proprietary Desktop picker or cloud restoration.virajchoudhary commentedon Oct 14, 2026
Publication follow-up to #29915 (comment).
The previously blocked commit and push succeeded through the supported approval path. The exact tested and independently reviewed candidate is now available on my fork: virajchoudhary@db540b7. The remote branch SHA was read back and matches the local commit. The committed six-file diff is byte-identical to the final tested patch.
The candidate applies and awaits explicit loaded-resume permission, approval-policy, and reviewer updates, rejects unknown or disallowed selections, preserves permitted legacy sandbox behavior despite unrelated profile fallback, and preserves explicit raw-config fork approval overrides. Typed approval overrides retain precedence. Immediate fresh-process restoration and reviewer transitions are covered.
Verification remains the previously reported 72 distinct integration cases plus one unit test under Rust 1.99, with all six loaded-resume cases and the strengthened two-case restoration coverage also passing on pinned Rust 1.95. Completed tests were not repeated because the patch did not change. No upstream PR or merge was created. This is a fork-only public-backend candidate, not a released or verified proprietary Desktop fix.
miloszjader-create commentedon Oct 15, 2026
Additional macOS reproduction with a named custom permission profile and filesystem-boundary impact.
I can reproduce behavior consistent with this issue in ChatGPT/Codex Desktop Work on macOS.
Configuration intent:
.gitand.codexinside the repository are configured read-only;sandbox_modeorsandbox_workspace_write.Observed sequence:
Approve for me.workspace-write.Controlled filesystem observations from the affected session:
.git: denied withOperation not permitted;.codex: denied withOperation not permitted;The created test files were disposable sentinels and were removed after verification. No existing project files, configuration, repository metadata, or network settings were modified.
This appears consistent with the permission-profile persistence problem described in this issue: the selected named profile is visible before submission, but the effective task state transitions to the legacy
workspace-writepath.Important limitation:
This does not establish the internal root cause, and I am not reporting a confirmed security breach. It is additional evidence that the profile-loss behavior can materially change the effective filesystem boundary for a task.
Expected behavior:
The selected named permission profile should remain the effective permission authority across task submission until the user explicitly changes it. If another policy overrides it, the UI should make that effective transition explicit before execution.
Feedback ID:
01a13e0e-6b5d-7f50-8a87-4ffd10ddeb0f
5edges-engineering commentedon Oct 15, 2026
Additional existing-remote-thread observation on Codex CLI 0.157.1.
The intended policy was the stricter
workspace-writesandbox withapproval_policy=neverfor an existing remote app-server thread. A single supportedcodex queueinvocation selected that sandbox and approval policy. The queue command exited successfully, and the same existing thread completed a deliberately no-tool permission-readback turn. Its effective turn context still showeddanger-full-accessandnever. No shell command, review, or product action was executed under the mismatched policy.Source inspection at
rust-v0.157.1(commit36650394c5b38c2990ccf2a3457165ca3e9d9726) explains this boundary:codex-rs/tui/src/session_queue_commands.rsconstructsThreadQueueAddwithout sandbox or approval fields. Accepted global CLI flags therefore do not establish that those settings reached the existing thread.codex-rs/tui/src/app_server_session.rsintentionally removes permission and approval overrides for remote resume. We did not attempt remote resume as a workaround.thread/settings/updatepath, but source presence does not establish a supported exact-thread control in the deployed client/server pair.Could the maintainers clarify the supported client operation for setting and authoritatively reading back BOTH
workspace-writeandneveron the same existing remote thread, including the server capability/version contract?A fail-closed improvement would reject ineffective permission flags on
queue, or apply and verify an explicit supported settings update before dispatch. Unsupported, rejected, or mismatched settings must not launch the requested work. Existing thread identity/history/model and unrelated sessions should remain unchanged.We have not edited persisted thread settings, changed global defaults, issued raw protocol calls, replaced the thread, or installed a fork. We saw the backend candidate already linked in this issue; it is not evidence of a released Desktop/client fix, and we have not repeated its reported tests or assumed it resolves the CLI dispatch boundary.
miloszjader-create commentedon Oct 15, 2026
Additional same-turn evidence from a macOS Desktop / Work reproduction.
I correlated the previously reported filesystem result with the saved turn/session metadata.
The sequence is now clearer:
In the preceding turn, the saved session state had the named permission profile active, with:
Before the next task, the named profile was still visibly selected in the UI according to the user observation.
The saved context for the next turn, however, contained:
In that same turn:
.gitwas denied;.codexwas denied;So the important point is not that an active READ rule was bypassed.
The stronger evidence is that the effective turn state changed between turns from:
named profile active + parent READ
to:
active profile null + parent WRITE
and the filesystem behavior in that same turn matched the broader WRITE policy.
This is consistent with the persistence / propagation problem described in this issue.
Important limitations:
The useful diagnostic chain is:
named profile + parent READ
→ active profile null + parent WRITE
→ same-turn parent write succeeds
The client/session metadata for this reproduction reported:
cli_version = 0.162.0-alpha.17.2I do not yet have the matching Desktop app version/build for this exact run.
This is additional evidence only; I am not claiming root cause.
miloszjader-create commentedon Oct 15, 2026
Additional same-session evidence.
I repeated the check without changing the model, project, configuration, or permissions manually.
In the same session:
Previous turn:
nereus-repo-only-v1gpt-5.6-terraBefore the next authorized enforcement check, the saved turn metadata had already changed to:
nullworkspace-writerestrictedgpt-5.6-terraBecause the policy had already changed before the filesystem test, I stopped without creating any sentinel or performing any write operation.
This is therefore not evidence of an enforcement bypass.
It is additional evidence of the persistence / propagation problem within the same session:
named profile active + parent READ
→ next turn: active profile null + parent WRITE
No model change, config change, permission change, filesystem change, or project move was performed between these observations.
This appears consistent with the core issue reported here: the selected named permission profile does not reliably persist as the effective policy across turns.GITHUB COMMENT DRAFT — #29915
Additional same-session evidence.
I repeated the check without changing the model, project, configuration, or permissions manually.
In the same session:
Previous turn:
nereus-repo-only-v1gpt-5.6-terraBefore the next authorized enforcement check, the saved turn metadata had already changed to:
nullworkspace-writerestrictedgpt-5.6-terraBecause the policy had already changed before the filesystem test, I stopped without creating any sentinel or performing any write operation.
This is therefore not evidence of an enforcement bypass.
It is additional evidence of the persistence / propagation problem within the same session:
named profile active + parent READ
→ next turn: active profile null + parent WRITE
No model change, config change, permission change, filesystem change, or project move was performed between these observations.
This appears consistent with the core issue reported here: the selected named permission profile does not reliably persist as the effective policy across turns.
miloszjader-create commentedon Oct 15, 2026
Additional same-session read-only diagnostic.
I checked whether the permission/path-policy state changes during execution of a single turn.
At START OF TURN, before any project read action:
I then performed exactly one harmless read-only action: listing the top-level names in the project root.
Immediately afterward, the same permission/path-policy state remained visible.
Result:
NO OBSERVED STATE CHANGE WITHIN THE TURN.
The broader parent WRITE policy was already present at the beginning of the turn.
This narrows the observed transition boundary:
previous turn:
parent = READ
next turn, before any project action:
parent = WRITE
after read-only action:
parent = WRITE
So the current evidence is more consistent with a between-turn / turn-initialization / state-rehydration transition than with a permission change triggered by the read-only task itself.
No write, sentinel, Gate B test, profile change, selector change, sandbox change, network action, or implementation was performed.
This still does not demonstrate bypass of an active READ rule.
The unresolved question remains why the narrower selected/intended permission state is not preserved into the next effective turn context.
miloszjader-create commentedon Oct 15, 2026
Additional persisted-session evidence from the same Codex Desktop thread.
I performed a read-only comparison of consecutive persisted
turn_contextrecords from:/Users/emj/.codex/sessions/2026/10/15/rollout-2026-10-15T18-52-45-01a1407a-ffae-7210-825b-36c13e9830f2.jsonl
Session:
01a1407a-ffae-7210-825b-36c13e9830f2
TURN N:
01a1407b-0456-7f70-b96c-1694c444b092
Persisted fields included:
TURN N+1:
01a14088-acb1-75b0-b8bb-8e110cb12da7
Persisted fields included:
CWD and workspace roots remained unchanged across both turns.
This means the state transition is present in the persisted turn context itself, not only in the UI.
Observed transition:
active named profile present
→ active named profile absent
parent READ
→ parent WRITE
no broadened writable roots
→ broadened writable roots present
No permission-source, override provenance, or rehydration marker was exposed in the persisted records, so this still does not identify the root cause.
This evidence supports a persisted between-turn permission/policy transition.
It does NOT demonstrate a sandbox enforcement bypass.
No write, sentinel, Gate B enforcement test, config change, profile change, raw protocol call, or thread-state edit was performed.
The remaining question is:
What supported Codex Desktop / app-server mechanism causes the named active permission profile and narrower path policy to disappear between consecutive persisted turn contexts while cwd/workspace identity remains unchanged?
miloszjader-create commentedon Oct 15, 2026
Additional narrowing based on persisted turn-context evidence and related public reports.
In my macOS Codex Desktop session, two consecutive persisted
turn_contextrecords show:TURN N:
TURN N+1:
cwd and workspace identity remained unchanged.
The broader policy was already present at the beginning of Turn N+1, before any project action.
This appears closely related to the symptom family reported in:
I am NOT claiming the same trigger or root cause.
Could maintainers clarify this specific transition:
When a Codex Desktop turn starts without an
active_permission_profile/ named permission id, can the client fall back to a legacysandboxPolicy/ workspace-write payload, causing the app-server to persist a broader permission snapshot and derive writable roots such as the cwd parent?More specifically:
Is named permission-profile state expected to persist across consecutive manual turns in the same Desktop thread?
If the named profile id is absent on a later turn, what is the documented fallback path?
Can that fallback derive or restore broader writable roots from Desktop workspace/thread state?
Is this behavior tracked under Permission / approval mode selection does not persist for new or existing threads #29915, Codex desktop: permission state (activePermissionProfile/approvalPolicy/writableRoots) silently degrades to :workspace + sandbox_approval: false over successive read-only turns in the same task #40698, or [Windows][Desktop][Automations] Scheduled heartbeat drops the selected named permission profile and sends legacy sandboxPolicy instead #50120?
Is there a supported way to authoritatively verify the permission payload/profile id that Desktop sends for each turn?
Observed runtime metadata:
cli_version = 0.162.0-alpha.17.2
No write, sentinel, Gate B enforcement test, config change, profile change, raw protocol call, or thread-state edit was performed for this diagnostic.
This still does not demonstrate an enforcement bypass; the observed problem is a persisted permission-state transition between turns.
miloszjader-create commentedon Oct 15, 2026
I have now compared two consecutive persisted
turn_contextrecords from the same manual Codex Desktop thread on macOS.Turn N:
Turn N+1:
The cwd and workspace identity remained unchanged.
A separate read-only diagnostic also showed that the broader parent-WRITE state was already present at the beginning of Turn N+1 and did not change after a harmless read-only directory listing.
This therefore appears to be a persisted between-turn permission-state transition, not an in-turn change caused by the task action.
Related public reports appear relevant:
I am not claiming these have the same trigger or exact root cause.
Could maintainers clarify:
Are named permission profiles expected to persist across consecutive manual Desktop turns in the same thread?
If a later turn has no active permission-profile id, what is the supported fallback behavior?
Can Desktop fall back to a legacy sandboxPolicy / workspace-write payload in that case?
Can such a legacy/default path replace the thread's named profile state?
How are writable roots derived in that fallback, specifically an entry such as:
/Users/emj/Documents/Codex
while the cwd remains:
/Users/emj/Documents/Codex/2026-09-23/referenced-chatgpt-conversation-this-is-an
What is the supported and authoritative way for a user to read back the profile id and filesystem roots actually applied to a specific Desktop turn?
Observed runtime metadata:
cli_version = 0.162.0-alpha.17.2
No write test, sentinel, config change, profile change, raw protocol call, persisted-thread edit, or sandbox-enforcement test was performed for this comparison.
This does not demonstrate an enforcement bypass. The observed finding is a persisted permission-state transition between consecutive turns.
miloszjader-create commentedon Oct 16, 2026
Post-update reproduction on the newer Codex Desktop build.
Environment:
Codex Desktop: 26.1013.31256
codex-cli: 0.163.0-alpha.10.2
macOS arm64
I repeated a strictly read-only consecutive-turn persistence check.
TURN A, immediately after selecting the narrow custom permission profile:
TURN B, immediately following Turn A, without re-selecting or changing the permission profile:
The same workspace/visualization identity was preserved.
No file write, sentinel, config change, permission change, sandbox change, network test, or enforcement test was performed.
active_permission_profilewas not exposed in either turn on this build, so I am not claiming that the named profile field itself disappeared.What is directly observed is that the filesystem permission state broadened between two consecutive manual turns:
parent READ
→ parent WRITE
This indicates that the permission-persistence symptom remains reproducible on Desktop 26.1013.31256 / CLI 0.163.0-alpha.10.2.
This still does not demonstrate a sandbox-enforcement bypass.
miloszjader-create commentedon Oct 16, 2026
GITHUB #29915
Additional exact-version diagnostic result.
Environment:
The issue remains reproducible on this build.
A read-only exact-version inspection of the installed Desktop bundle showed that, when:
useAppServerPermissionDefault = true
the outgoing named permission profile is intentionally omitted from the next turn request:
requestPermissionProfile = null
The relevant Desktop logic computes default mode from inputs equivalent to:
usePermissionSelection
OR
(
contextDefault
??
(!explicitRequest AND latestDefault)
)
For the historical affected turn, logs confirm:
useAppServerPermissionDefault = true
requestPermissionProfile = null
and the following persisted turn broadened the parent path from READ to WRITE.
The exact historical operand that activated default mode could not be recovered from persisted local records, so the trigger remains UNVERIFIED.
Important correction to earlier interpretation:
the named profile returned by thread/resume is not simply ignored. Exact-version bundle logic can restore it after resume. The observed loss occurs later when the following turn is prepared using app-server permission default mode.
Therefore current evidence supports:
Could maintainers clarify which Desktop state or UI path is expected to set
useAppServerPermissionDefaultfor a normal manual follow-up turn when a named permission profile is active?miloszjader-create commentedon Oct 16, 2026
Additional current-build reproduction with a narrowly bounded effective-policy transition.
Environment:
nereus-repo-only-v1Project-local config contains:
default_permissions = "nereus-repo-only-v1"No legacy
sandbox_modeorsandbox_workspace_writewas found in the checked local configuration layers.The affected thread is:
01a143c8-ca61-7983-9f11-8dc6fe4e8497The session transcript records this exact sequence:
10:24:19.945 CEST
thread_settings_appliedactive_permission_profile = nereus-repo-only-v1/Users/emj/Documents/Codex = READ10:25:24.271 CEST
thread_settings_appliedactive_permission_profilefield absent/Users/emj/Documents/Codex = WRITE10:25:24.325 CEST
affected task starts
10:25:25.265 CEST
affected
turn_contextsandbox_policy.type = workspace-writeField-level comparison shows:
on-request;user;.gitand.codexremain READ.There is no recorded user permission-selection event between the last narrow state and the replacement state.
There is also no recorded
thread/resume, explicit reconciliation event, or other event in the rollout between 10:24:19.945 and 10:25:24.271.The affected turn therefore starts after the broader policy state has already been applied.
Important qualification:
This does NOT demonstrate a sandbox escape or bypass of a contemporaneously active READ policy.
What is demonstrated is an effective recorded policy transition:
named profile / parent READ
→
thread_settings_appliedreplacement with profile omitted / parent WRITE→ following turn inherits
workspace-write/ parent WRITE.The remaining unresolved question is provenance.
Could maintainers clarify:
Which component supplies or generates the replacement payload represented by
thread_settings_applied?Under what supported path can an existing named
active_permission_profiledisappear while the parent path simultaneously becomes writable?Can
turn/start, app-server defaults, thread state inheritance, or Desktop reconciliation legitimately produce this transition without an explicit user permission change?Is this expected behavior, or does this reproduction indicate a permission-profile lifecycle/state-propagation defect?
Is there a supported workaround or fixed build that preserves the named profile across subsequent turns without broadening the parent filesystem boundary?
Private Codex feedback was also submitted with identifier:
no-active-thread-01a1450b-355e-7901-a2e2-9d4e823e2f8cI can provide the sanitized two
thread_settings_appliedrecords and the surrounding rollout window if useful.miloszjader-create commentedon Oct 17, 2026
Final evidence update for #29915
Headline finding:
In Codex Desktop, within a single thread that started correctly with:
requestPermissionProfile = nereus-repo-only-v1
requestSandboxPolicyType = null
useAppServerPermissionDefault = false
and retained:
activePermissionProfile = nereus-repo-only-v1
Parent = READ
two later requests were directly retained with:
requestPermissionProfile = null
requestSandboxPolicyType = workspaceWrite
useAppServerPermissionDefault = true
and each was followed by:
Parent = WRITE
The project-local configuration itself resolves correctly in a clean standalone App Server and in fresh Desktop threads.
Environment:
Codex Desktop:
26.1013.40934
Build:
20770
Bundled CLI:
0.163.0-alpha.10.2
Target repository:
/Users/emj/Documents/Codex/2026-09-23/referenced-chatgpt-conversation-this-is-an
Project-local profile:
nereus-repo-only-v1
Project configuration SHA-256:
ab6abb38d0a60514b9baf628bf3102ee57e5f5a495b9bc8a86d3012fca5247c0
Expected project boundary:
Parent /Users/emj/Documents/Codex = READ
Exact repository = WRITE
.git = READ
.codex = READ
CONTROL RESULTS
A clean standalone App Server launched with the exact repository CWD resolved:
default_permissions = nereus-repo-only-v1
configRequirements/read = null
sandbox_mode = null
sandbox_workspace_write = null
permissionProfile/list:
nereus-repo-only-v1 = allowed
Two fresh Desktop threads also initialized correctly.
Correct fresh thread 1:
requestPermissionProfile = nereus-repo-only-v1
requestSandboxPolicyType = null
useAppServerPermissionDefault = false
requestApprovalPolicy = on-request
Retained state:
activePermissionProfile = nereus-repo-only-v1
Parent = READ
Repository = WRITE
.git = READ
.codex = READ
filesystem type = restricted
Correct fresh thread 2 showed the same pattern.
DIVERGENT THREAD
Thread:
01a1490f-e763-7170-a2ea-c1cba51540fa
The thread itself initially started correctly with the named-profile / Parent-READ request path.
Later transition:
10:59:05.093+02:00
requestPermissionProfile = null
requestSandboxPolicyType = workspaceWrite
useAppServerPermissionDefault = true
Corresponding retained state:
activePermissionProfile = null / not exposed depending on source
Parent = WRITE
At:
11:01:58.084+02:00
Desktop retained a:
thread/settings/update
event.
At the exact same timestamp, the session retained:
activePermissionProfile = nereus-repo-only-v1
Parent = READ
permission profile type = managed
filesystem type = restricted
approval policy = on-request
The submitted request payload for this thread/settings/update is not retained in the documented local Desktop diagnostics.
Then at:
11:02:05.783+02:00
the divergent request pattern appeared again:
requestPermissionProfile = null
requestSandboxPolicyType = workspaceWrite
useAppServerPermissionDefault = true
followed again by:
Parent = WRITE
So the directly retained sequence is:
correct named-profile / Parent READ
→
explicit null profile request + workspaceWrite + App Server default / Parent WRITE
→
named-profile / Parent READ
→
explicit null profile request + workspaceWrite + App Server default / Parent WRITE
REQUEST-PATH COMPARISON
Correct requests:
requestPermissionProfile = nereus-repo-only-v1
requestSandboxPolicyType = null
useAppServerPermissionDefault = false
Divergent requests:
requestPermissionProfile = null
requestSandboxPolicyType = workspaceWrite
useAppServerPermissionDefault = true
This request-path difference is directly retained.
LOCAL OVERRIDE / MANAGED-SOURCE CHECK
No locally visible source was found for the divergent selection.
Checked:
/etc/codex/requirements.toml = absent
/etc/codex/managed_config.toml = absent
macOS MDM requirements_toml_base64 = absent
No locally visible:
sandbox_mode
sandbox_workspace_write
--sandbox
--config
--profile
override was identified.
KNOWN EVIDENCE GAP
The full submitted request payload for the 11:01:58 thread/settings/update is not retained by the documented Desktop logs/session sources.
The available evidence therefore establishes the alternating policy state and the divergent request path at 10:59 and 11:02, but does not establish the exact request payload that restored the named profile at 11:01.
CLAIM BOUNDARIES
I am NOT claiming:
No write probe was performed after the inconsistent preconditions were observed.
The effective enforcement boundary remains unverified.
The observed issue is:
A correctly initialized repository-bound thread later issued requests through a different permission/sandbox request path, switching from the named profile / Parent READ state to explicit-null profile + workspaceWrite + App Server default / Parent WRITE.
The trigger for that request-path change remains unattributed.
Feedback ID:
01a13e0e-6b5d-7f50-8a87-4ffd10ddeb0f
ViciousSeagull commentedon Oct 17, 2026
Additional Windows desktop occurrence: the selected review mode is not reflected in subsequent execution.
Environment checked October 17, 2026:
Observed sequence:
The denied operation has not been replayed. The retained application work is preserved. No security settings, global defaults, executors, or enforcement code were changed.
This may relate to #29915 and the command-classification behavior discussed in #40060. PR #54076 is merged, but its description preserves conservative handling of script state, pipelines, and wrappers. Its inclusion in this desktop runtime and applicability to this incident have not been established. The original denial's actual enforcing rule and cause remain unknown.
Please provide:
This report deliberately excludes local paths, project names, raw transcripts, credentials, and account or session identifiers.