Skip to content

Permission / approval mode selection does not persist for new or existing threads #29915

Open

Description

@andrewsohrabi

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

  1. Open Codex desktop.
  2. Start a new thread.
  3. In the permission / approval picker, choose a non-workspace mode, for example Full access.
  4. Send the first prompt / start the thread.
  5. 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

  1. Start a thread with default / read-only-ish settings.
  2. Materialize the rollout with a simple turn.
  3. Resume or switch back to the already-loaded thread while requesting different approval / permission settings, for example approvalPolicy plus permissions = ":danger-full-access".
  4. 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:

  1. Loaded-thread backend fix:
    andrewsohrabi@94e81ea

    This adds a regression test for loaded thread/resume applying approvalPolicy + permissions, then routes supported loaded-resume overrides through the same validated thread-settings path used by thread/settings/update. Startup-only fields such as config, baseInstructions, and developerInstructions remain ignored while the thread is already loaded.

  2. New-thread schema / client-contract fix:
    andrewsohrabi@d39d601

    This adds coverage proving stable generated TypeScript includes permission-profile selection for thread lifecycle requests, then exposes permissions plus activePermissionProfile for thread/start, thread/resume, and thread/fork in 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_params
  • just test -p codex-app-server-protocol
  • just 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

  1. added
    bugSomething isn't working
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    sandboxIssues related to permissions or sandboxing
    on Jun 24, 2026
  2. github-actions commented on Jun 24, 2026

    @github-actions
    Contributor
  3. andrewsohrabi commented on Jun 24, 2026

    @andrewsohrabi
    Author

    Potential duplicates detected. Please review them and close your issue if it is a duplicate.

    Powered by Codex Action

    Not a duplicate

  4. andrewsohrabi commented on Jun 24, 2026

    @andrewsohrabi
    Author

    Correction: @codex mentions 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.

  5. andrewsohrabi commented on Jun 25, 2026

    @andrewsohrabi
    Author

    Suggested fix candidates:

    The first commit adds a regression test for loaded thread/resume applying approvalPolicy + permissions, then routes supported loaded-resume overrides through the same validated thread-settings path used by thread/settings/update. Startup-only fields such as config, baseInstructions, and developerInstructions remain ignored while the thread is already loaded.

    The second commit covers the new-thread symptom: stable generated protocol fixtures now expose permissions and activePermissionProfile for thread/start, thread/resume, and thread/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-server was attempted, but this managed sandbox hits unrelated local failures around nested macOS sandbox-exec, a missing target/debug/codex helper binary, and a few file-watch / sandboxed file-read timeouts.

  6. changed the title [-]Permission / approval mode does not persist when changed on existing threads[/-] [+]Permission / approval mode selection does not persist for new or existing threads[/+] on Jun 25, 2026
  7. added a commit that references this issue on Jun 25, 2026
    d39d601
  8. metrovoc commented on Aug 3, 2026

    @metrovoc

    Additional macOS reproduction on the current Codex Desktop build, linked to a private feedback upload.

    Feedback ID: 019fc598-b009-7150-a7a9-c83dacc379a6

    Environment:

    • Codex App 26.727.51351, build 6119
    • Bundled Codex runtime 0.146.0-alpha.9.2
    • macOS Darwin 25.6.0, arm64

    Relevant configuration:

    default_permissions = "Auto"
    approval_policy = "on-request"
    approvals_reviewer = "auto_review"
    
    [permissions.Auto]
    extends = ":workspace"
    
    [permissions.Auto.network]
    enabled = true
    
    [permissions.Auto.network.domains]
    "*" = "allow"

    Observed in the same existing non-Goal thread without changing config.toml:

    1. A submission uses activePermissionProfile.id = "Auto" and the profile's network policy is enabled.
    2. A later submission uses activePermissionProfile = null, approvalPolicy = "on-request", and sandboxPolicy.type = "workspaceWrite". The UI then shows Approve for me.
    3. Re-selecting Auto restores the named profile temporarily.
    4. A subsequent turn can switch back to activePermissionProfile = null again.

    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.

  9. BeautyToolsAU commented on Sep 15, 2026

    @BeautyToolsAU

    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

    1. Create a new chat; Full Access is already displayed.
    2. Request an authorised change.
    3. The task behaves as restricted.
    4. Toggle to Require approval, then back to Full Access.
    5. Retry the operation.

    Evidence independently observed in this task

    Before the user corrected the permission selection:

    • The runtime exposed sandbox_mode = workspace-write, approval_policy = never, and a managed/restricted filesystem profile.
    • Connected-app reads succeeded.
    • An authorised draft update repeatedly failed before execution with: MCP tool call requires approval, but approval policy is never.
    • Read-only shell commands also failed before execution with helper_sandbox_lock_failed and SetNamedSecurityInfoW sandbox dir failed: 5.

    After the user reported fixing permissions:

    • The runtime exposed sandbox_mode = danger-full-access and an unrestricted filesystem profile. Approval policy remained never.
    • Local read commands succeeded.
    • The previously rejected draft update succeeded, and its response contained the requested changes.

    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.

  10. edwardswhiskeyblues-alt commented on Oct 6, 2026

    @edwardswhiskeyblues-alt

    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

    • Codex/ChatGPT Desktop: 26.928.31416
    • Windows x64, OS build 26300
    • Per-session Codex runtimes recorded in the two affected rollouts: 0.149.0-alpha.4.1 and 0.159.2
    • Global config remained default_permissions = ":danger-full-access"; that file had not changed during either transition.

    Observed timeline (UTC, 2026-10-05 / 2026-10-06)

    1. Existing thread A had been operating with Full Access (danger-full-access + approval_policy=never). At 15:10:53Z, its next submitted turn instead carried workspace-write + approval_policy=on-request + approvals_reviewer=auto_review (the “Approve for me” preset). There was no deliberate permission action by the user.
    2. Existing thread B independently changed at 20:05:13Z. Its rollout recorded thread_settings_applied with the managed/workspace profile immediately before task start/compaction, again without a standalone user-authored settings change.
    3. The user manually restored thread B at 04:03:31Z and thread A at 04:06:00Z. Subsequent state/turn context correctly showed active_permission_profile=:danger-full-access, sandbox_policy=danger-full-access, and approval_policy=never in 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 never

    That 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 an on-request profile 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, and 04:23:55Z: three separate thread_settings_applied events were emitted.
    • Every event carried approval_policy=on-request, approvals_reviewer=user, active_permission_profile=:workspace, and the managed workspace filesystem policy.
    • The persisted thread row now also stores approval_mode=on-request with 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

    • An existing thread’s selected permission profile should remain unchanged until the user explicitly changes it or an enforced policy change is clearly surfaced.
    • The UI and persisted/runtime state should identify the source and time of any automatic permission reset.
    • A successful selector change should update the visible checkmark/label immediately and show the effective pending profile before the next turn; failed selections should not emit settings events.
    • If a tool requires an approval that cannot exist under Full Access, the app should explain the incompatibility and offer the compatible mode instead of returning an internal policy dead end.

    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.

  11. StutteringJohnMelendez commented on Oct 7, 2026

    @StutteringJohnMelendez

    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/resume and thread/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:

    • Which supported operation durably changes a cloud-backed conversation's permission profile?
    • How should clients determine whether the receiver supports thread/settings/update?
    • What should the UI display when an update is unavailable, rejected, or only held in local cached state?
    • What released fix and verification procedure demonstrate that the selected, acknowledged, and effective settings agree after resume and a normal restart?
    • Is a supported repair available for already-affected conversations without replacing them?

    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.

  12. info618 commented on Oct 10, 2026

    @info618

    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.

  13. 1 remaining item

  14. virajchoudhary commented on Oct 13, 2026

    @virajchoudhary

    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 to user, 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 commit after this test-only revision was again refused before execution with approval 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.

  15. virajchoudhary commented on Oct 14, 2026

    @virajchoudhary

    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.

  16. miloszjader-create commented on Oct 15, 2026

    @miloszjader-create

    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:

    • a named permission profile is selected before task submission;
    • the profile inherits from a read-only baseline;
    • the broader parent workspace is configured read-only;
    • the exact repository is configured writable;
    • .git and .codex inside the repository are configured read-only;
    • there is no project-local sandbox_mode or sandbox_workspace_write.

    Observed sequence:

    1. A new Work project is opened and bound to the intended repository.
    2. The named permission profile is visibly selected in the permissions dropdown before submission.
    3. A task is submitted.
    4. After submission, the UI changes to Approve for me.
    5. Runtime/session metadata reports workspace-write.
    6. Runtime metadata exposes a broader writable root than the named profile intends.

    Controlled filesystem observations from the affected session:

    • write directly inside the exact repository: succeeded;
    • write under repository .git: denied with Operation not permitted;
    • write under repository .codex: denied with Operation not permitted;
    • write outside the exact repository but inside the broader workspace hierarchy: succeeded.

    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-write path.

    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

  17. 5edges-engineering commented on Oct 15, 2026

    @5edges-engineering

    Additional existing-remote-thread observation on Codex CLI 0.157.1.

    The intended policy was the stricter workspace-write sandbox with approval_policy=never for an existing remote app-server thread. A single supported codex queue invocation 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 showed danger-full-access and never. No shell command, review, or product action was executed under the mismatched policy.

    Source inspection at rust-v0.157.1 (commit 36650394c5b38c2990ccf2a3457165ca3e9d9726) explains this boundary:

    • codex-rs/tui/src/session_queue_commands.rs constructs ThreadQueueAdd without 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.rs intentionally removes permission and approval overrides for remote resume. We did not attempt remote resume as a workaround.
    • The TUI has a thread/settings/update path, 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-write and never on 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.

  18. miloszjader-create commented on Oct 15, 2026

    @miloszjader-create

    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:

    1. In the preceding turn, the saved session state had the named permission profile active, with:

      • parent workspace = READ
      • exact repository = WRITE
    2. Before the next task, the named profile was still visibly selected in the UI according to the user observation.

    3. The saved context for the next turn, however, contained:

      • active permission profile = null
      • permission profile type = managed
      • sandbox = workspace-write
      • filesystem = restricted
      • parent workspace = WRITE
      • exact repository = WRITE
    4. In that same turn:

      • current working directory was the exact repository;
      • write directly inside the repository succeeded;
      • write under .git was denied;
      • write under .codex was denied;
      • creation of a disposable sentinel directly in the broader parent workspace succeeded;
      • existence was confirmed;
      • the sentinel was then removed and cleanup was confirmed.

    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 UI selection before submission is user-observed, not independently captured in the turn metadata;
    • the selector state after submission is not available for this exact turn;
    • the project profile definition was recorded from an earlier read, not re-read during the T5 turn;
    • the internal cause of the transition remains unknown;
    • this does not establish a separate sandbox-enforcement vulnerability.

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

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

  19. miloszjader-create commented on Oct 15, 2026

    @miloszjader-create

    Additional same-session evidence.

    I repeated the check without changing the model, project, configuration, or permissions manually.

    In the same session:

    Previous turn:

    • active permission profile: nereus-repo-only-v1
    • parent workspace: READ
    • repository: WRITE
    • model: gpt-5.6-terra

    Before the next authorized enforcement check, the saved turn metadata had already changed to:

    • active permission profile: null
    • parent workspace: WRITE
    • repository: WRITE
    • sandbox: workspace-write
    • filesystem: restricted
    • model still: gpt-5.6-terra

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

    • active permission profile: nereus-repo-only-v1
    • parent workspace: READ
    • repository: WRITE
    • model: gpt-5.6-terra

    Before the next authorized enforcement check, the saved turn metadata had already changed to:

    • active permission profile: null
    • parent workspace: WRITE
    • repository: WRITE
    • sandbox: workspace-write
    • filesystem: restricted
    • model still: gpt-5.6-terra

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

  20. miloszjader-create commented on Oct 15, 2026

    @miloszjader-create

    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:

    • cwd = /Users/emj/Documents/Codex/2026-09-23/referenced-chatgpt-conversation-this-is-an
    • permission_profile = managed
    • file_system = restricted
    • project root = WRITE
    • direct parent = WRITE, via /Users/emj/Documents/Codex
    • .git = READ
    • .codex = READ
    • active_permission_profile was not exposed

    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.

  21. miloszjader-create commented on Oct 15, 2026

    @miloszjader-create

    Additional persisted-session evidence from the same Codex Desktop thread.

    I performed a read-only comparison of consecutive persisted turn_context records 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:

    • permission_profile = managed
    • active_permission_profile = nereus-repo-only-v1
    • active_permission_profile extends :read-only
    • approval_policy = on-request
    • sandbox_policy.type = workspace-write
    • file_system = restricted
    • project root = WRITE
    • direct parent = READ
    • .git = READ
    • .codex = READ

    TURN N+1:
    01a14088-acb1-75b0-b8bb-8e110cb12da7

    Persisted fields included:

    • permission_profile = managed
    • active_permission_profile = absent
    • approval_policy = on-request
    • sandbox_policy.type = workspace-write
    • file_system = restricted
    • writable_roots = present and includes /Users/emj/Documents/Codex
    • project root = WRITE
    • direct parent = WRITE
    • .git = READ
    • .codex = READ

    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?

  22. miloszjader-create commented on Oct 15, 2026

    @miloszjader-create

    Additional narrowing based on persisted turn-context evidence and related public reports.

    In my macOS Codex Desktop session, two consecutive persisted turn_context records show:

    TURN N:

    • active_permission_profile = nereus-repo-only-v1
    • extends = :read-only
    • parent = READ
    • project root = WRITE

    TURN N+1:

    • active_permission_profile = absent
    • writable_roots = present
    • writable_roots includes /Users/emj/Documents/Codex
    • parent = WRITE
    • project root = WRITE

    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 legacy sandboxPolicy / workspace-write payload, causing the app-server to persist a broader permission snapshot and derive writable roots such as the cwd parent?

    More specifically:

    1. Is named permission-profile state expected to persist across consecutive manual turns in the same Desktop thread?

    2. If the named profile id is absent on a later turn, what is the documented fallback path?

    3. Can that fallback derive or restore broader writable roots from Desktop workspace/thread state?

    4. 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?

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

  23. miloszjader-create commented on Oct 15, 2026

    @miloszjader-create

    I have now compared two consecutive persisted turn_context records from the same manual Codex Desktop thread on macOS.

    Turn N:

    • active_permission_profile = nereus-repo-only-v1
    • extends = :read-only
    • permission_profile = managed
    • approval_policy = on-request
    • project root = WRITE
    • parent = READ

    Turn N+1:

    • active_permission_profile = absent
    • permission_profile = managed
    • approval_policy = on-request
    • writable_roots = present
    • writable_roots includes /Users/emj/Documents/Codex
    • project root = WRITE
    • parent = WRITE

    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:

    1. Are named permission profiles expected to persist across consecutive manual Desktop turns in the same thread?

    2. If a later turn has no active permission-profile id, what is the supported fallback behavior?

    3. Can Desktop fall back to a legacy sandboxPolicy / workspace-write payload in that case?

    4. Can such a legacy/default path replace the thread's named profile state?

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

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

  24. miloszjader-create commented on Oct 16, 2026

    @miloszjader-create

    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:

    • project root = WRITE
    • /Users/emj/Documents/Codex = READ
    • .git = READ
    • .codex = READ
    • writable root limited to the project

    TURN B, immediately following Turn A, without re-selecting or changing the permission profile:

    • project root = WRITE
    • /Users/emj/Documents/Codex = WRITE
    • .git = READ
    • .codex = READ
    • /Users/emj/Documents/Codex appeared as an additional writable root

    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_profile was 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.

  25. miloszjader-create commented on Oct 16, 2026

    @miloszjader-create

    GITHUB #29915

    Additional exact-version diagnostic result.

    Environment:

    • Codex Desktop 26.1013.31256
    • codex-cli 0.163.0-alpha.10.2
    • macOS arm64

    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:

    • default-mode activation rule: verified
    • named profile omitted when default mode is active: supported
    • exact historical trigger: unverified
    • sandbox enforcement bypass: not demonstrated

    Could maintainers clarify which Desktop state or UI path is expected to set useAppServerPermissionDefault for a normal manual follow-up turn when a named permission profile is active?

  26. miloszjader-create commented on Oct 16, 2026

    @miloszjader-create

    Additional current-build reproduction with a narrowly bounded effective-policy transition.

    Environment:

    • macOS arm64
    • Codex Desktop 26.1013.31256
    • bundled CLI 0.163.0-alpha.10.2
    • project-local named permission profile:
      nereus-repo-only-v1

    Project-local config contains:

    default_permissions = "nereus-repo-only-v1"

    No legacy sandbox_mode or sandbox_workspace_write was found in the checked local configuration layers.

    The affected thread is:

    01a143c8-ca61-7983-9f11-8dc6fe4e8497

    The session transcript records this exact sequence:

    10:24:19.945 CEST

    thread_settings_applied

    • active_permission_profile = nereus-repo-only-v1
    • parent /Users/emj/Documents/Codex = READ
    • exact project root = WRITE

    10:25:24.271 CEST

    thread_settings_applied

    • active_permission_profile field absent
    • parent /Users/emj/Documents/Codex = WRITE
    • exact project root remains WRITE

    10:25:24.325 CEST

    affected task starts

    10:25:25.265 CEST

    affected turn_context

    • sandbox_policy.type = workspace-write
    • parent = WRITE
    • exact project root = WRITE
    • no named active permission profile recorded

    Field-level comparison shows:

    • named permission profile removed from the recorded settings state;
    • parent changes READ → WRITE;
    • exact project root remains WRITE;
    • cwd and workspace root remain the exact project root;
    • approval policy remains on-request;
    • reviewer remains user;
    • .git and .codex remain 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_applied replacement with profile omitted / parent WRITE

    → following turn inherits workspace-write / parent WRITE.

    The remaining unresolved question is provenance.

    Could maintainers clarify:

    1. Which component supplies or generates the replacement payload represented by thread_settings_applied?

    2. Under what supported path can an existing named active_permission_profile disappear while the parent path simultaneously becomes writable?

    3. Can turn/start, app-server defaults, thread state inheritance, or Desktop reconciliation legitimately produce this transition without an explicit user permission change?

    4. Is this expected behavior, or does this reproduction indicate a permission-profile lifecycle/state-propagation defect?

    5. 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-9d4e823e2f8c

    I can provide the sanitized two thread_settings_applied records and the surrounding rollout window if useful.

  27. miloszjader-create commented on Oct 17, 2026

    @miloszjader-create

    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:

    • a sandbox bypass;
    • enforcement failure;
    • security compromise;
    • malicious activity;
    • UI causation;
    • that thread/settings/update caused the divergence;
    • that the root cause is a managed source, Desktop bug, or server-side override.

    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

  28. ViciousSeagull commented on Oct 17, 2026

    @ViciousSeagull

    Additional Windows desktop occurrence: the selected review mode is not reflected in subsequent execution.

    Environment checked October 17, 2026:

    • Running Windows desktop package: OpenAI.Codex 26.1013.4894.0.
    • Executable used by the running Codex backend reports: codex-cli 0.163.0-alpha.10.2.
    • Windows 10.0.19045; PowerShell 7.6.7.

    Observed sequence:

    1. An authorized, local synthetic verification operation was rejected on October 12 before process creation with "CreateProcess ... rejected: blocked by policy". The operation involved generating a local preview, starting an owned hidden loopback preview service, and checking its served content.
    2. The owner reported selecting Approve for me in the existing worker chat.
    3. Subsequent retained worker turn records on October 16 still showed approval_policy=never, approvals_reviewer=user, and sandbox_mode=danger-full-access. The desktop's recorded turn-start requests supplied null permission overrides and used app-server defaults.
    4. A completed read-only configuration diagnostic found guardian_approval enabled, requirements=null in that diagnostic process, and user defaults matching the old mode. That separate process does not establish every requirement of the loaded desktop runtime.

    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:

    • A supported repair for permission-mode propagation in the existing desktop worker.
    • A way to identify the original enforcing rule and obtain its scoped disposition or eligible exact-action review.
    • The supported Windows desktop release containing any applicable fix, including its coverage limits.

    This report deliberately excludes local paths, project names, raw transcripts, credentials, and account or session identifiers.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appbugSomething isn't workingsandboxIssues related to permissions or sandboxingsessionIssues involving session (thread) management, resuming, forking, naming, archiving

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions