Skip to content

Windows Desktop: new threads/handoffs persist as on-request/workspaceWrite instead of inheriting visible Full Access state #25810

Open

Description

@andrewkangkr

Summary

On Windows Codex Desktop, newly created threads can persist as approvalPolicy = on-request / sandboxPolicy.type = workspaceWrite even when the user workflow appears to be operating in a Full Access-like mode.

This is reproducible with:

  • ordinary fresh test threads
  • handoff-created threads

and does not require Goal auto-continuation.

Environment

  • Codex Desktop app: OpenAI.Codex_26.527.7698.0_x64__2p2nqsd0c76g0
  • Platform: Windows

Expected

If the user has switched into a Full Access-like mode in the app, newly created threads should inherit that mode consistently, or the UI should clearly indicate that new threads will still be created with approval prompts.

Actual

Newly created threads are persisted as:

  • approvalPolicy = on-request
  • approvalsReviewer = guardian_subagent
  • sandboxPolicy.type = workspaceWrite

This then causes approval prompts to originate from those new threads.

Repro

  1. Open Codex Desktop on Windows.
  2. Work in a thread until the UI/workflow suggests Full Access-like behavior is active.
  3. Create a new thread from that session, or create a handoff into a new thread.
  4. Let the new thread do almost nothing, e.g. only answer once with a short reply.
  5. Inspect persisted thread state in C:\Users\강인철\.codex\.codex-global-state.json.
  6. Observe that the new thread is stored as on-request / guardian_subagent / workspaceWrite.

Concrete repro evidence

Host-level persisted mode in app state:

  • agent-mode-by-host-id.local = guardian-approvals
  • preferred-non-full-access-agent-mode-by-host-id.local = guardian-approvals

Fresh / recent threads created during reproduction:

  • current thread 019e866c-0a1c-7601-a60c-686b686bcc5c -> on-request / guardian_subagent / workspaceWrite
  • test thread 019e8764-2aa9-73d2-9b3d-87ecb202af5a -> on-request / guardian_subagent / workspaceWrite
  • test thread 019e8765-f908-7410-b44d-443df7934c73 -> on-request / guardian_subagent / workspaceWrite
  • test thread 019e877d-fb12-7683-8033-3f909b8ca714 -> on-request / guardian_subagent / workspaceWrite
  • handoff-derived thread 019e8689-b909-7ed1-a141-d0b3323a21cf -> on-request / guardian_subagent / workspaceWrite

Additional pattern:

  • among currently stored thread states on this machine, only 1 thread had approvalPolicy = never
  • 60 threads had approvalPolicy = on-request

Why this seems like a bug

This does not look like a one-off stuck thread.

It looks like one of:

  • thread creation is not inheriting the currently visible Full Access selection
  • host-level permission mode is not being committed correctly before thread creation
  • handoff/new-thread creation is using a different policy source than the visible UI state
  • the app is exposing a misleading Full Access state to the user while still creating threads in guardian-approvals mode

Related

This may be related to, but broader than, the behavior discussed in #24300.

I also added Windows reproduction details there in a comment:

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    windows-osIssues related to Codex on Windows systems
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    sandboxIssues related to permissions or sandboxing
    on Jun 2, 2026
  2. andrewkangkr commented on Jun 2, 2026

    @andrewkangkr
    Author

    Small follow-up after checking the related discussion in #24300:

    • an OpenAI collaborator there said the currently known issue can be worked around by sending a dummy prompt after changing permissions and before creating a Goal
    • however, my Windows Desktop repro appears broader than that specific Goal-related case

    In my reproduction:

    • this happens even without Goal auto-continuation
    • it also happens for newly created test threads that already received a user prompt and then stopped

    Concrete examples from my local persisted thread state:

    • 019e8764-2aa9-73d2-9b3d-87ecb202af5a -> on-request / guardian_subagent / workspaceWrite
    • 019e8765-f908-7410-b44d-443df7934c73 -> on-request / guardian_subagent / workspaceWrite
    • 019e877d-fb12-7683-8033-3f909b8ca714 -> on-request / guardian_subagent / workspaceWrite

    Those were fresh test threads that only received a short user prompt and replied once.

    So if this is the same root cause, it seems to affect thread creation / policy inheritance more generally on Windows Desktop, not only the "change permission -> create Goal" path.

  3. andrewkangkr commented on Jun 3, 2026

    @andrewkangkr
    Author

    Additional Windows Desktop follow-up from June 3, 2026:

    I have a clearer split now between auto-review vs thread policy.

    What I am seeing is not just "auto-review failed to approve something".
    Instead, the underlying thread policy itself appears to revert back to restrictive settings after thread execution starts.

    New observation

    After switching the app-level host mode to Full Access, I could verify the persisted host value became:

    • agent-mode-by-host-id.local = full-access

    I then created fresh threads and briefly saw cases that looked consistent with:

    • approvalPolicy = never
    • sandboxPolicy.type = dangerFullAccess

    However, after the thread actually started running / continuing, persisted state later showed the same thread as:

    • approvalPolicy = on-request
    • sandboxPolicy.type = workspaceWrite

    Why this matters

    This suggests the problem may be broader than "new thread creation inherits the wrong initial settings".
    It may also involve policy reversion during thread start / resume / goal-setting / delegated execution.

    So the bug shape now looks more like:

    1. visible/app-level Full Access can be selected successfully
    2. a new thread may initially appear consistent with Full Access
    3. once the thread actually begins execution, persisted thread policy can be rewritten back to on-request / workspaceWrite
    4. approval popups then come from that downgraded persisted policy

    Important distinction: auto-review vs approval policy

    At least in my local investigation, auto-review seems to be a separate concept from the popup itself:

    • auto-review / reviewer affects who reviews an approval request
    • approvalPolicy determines whether the thread is effectively never vs on-request

    So the user-facing symptom can look like "auto-review is broken", while the deeper issue is actually that the thread got persisted back to on-request.

    Scope note

    This still does not look Goal-only to me.
    I have seen related behavior in:

    • ordinary test threads
    • handoff/delegated threads
    • a Goal-adjacent AKN thread where the thread log later said it set an active goal for itself

    So Goal may be one trigger path, but not the whole story.

    If useful, I can provide another tightly scoped repro sequence specifically for:

    • create thread under Full Access
    • verify persisted never / dangerFullAccess
    • let thread actually start/continue
    • observe persisted reversion to on-request / workspaceWrite
  4. andrewkangkr commented on Jun 3, 2026

    @andrewkangkr
    Author

    Additional Windows Desktop / mobile-resume detail that seems worth tracking separately even if the original approvalPolicy drift is fixed.

    New behavior observed on June 4, 2026 (KST):

    • I had a parked handoff thread whose explicit instruction was "do not set a goal or proceed; just park the handoff".
    • The parked thread was projectless / handoff-shell style, with cwd like:
      C:\Users\<USER>\Documents\Codex\2026-06-03\specialization-layer-handoff-parked
    • The canonical source-of-truth repo for the work was elsewhere:
      C:\Users\<USER>\Documents\Codex\2026-05-24\hermes-project-os-expert-agent\expert-repository-poc

    After resuming from mobile and sending short messages like:

    • "남은 slice 2개인거지?"
    • "frontier는 어떤 상태?"
    • "1순위부터 해보자"

    the parked thread stopped behaving like a parked handoff and resumed actual work. It started trying to read/edit files in the canonical repo outside the current workspace and produced approval prompts such as:

    • "allow reading and then editing the canonical expert repo outside the current workspace..."
    • reads of tools/validate.py
    • reads of tools/test_validate_specialization.py
    • planned patching of latest.md

    In other words, the trigger was not a broad new user task from scratch; it was a few short follow-up messages inside a parked handoff thread. The app seems to treat that parked thread as a live execution surface even though its workspace is only a handoff shell, not the real repo.

    Separately, one of these parked threads later hit a Windows stale-path resume error even though the JSONL file still existed on disk. The error compared:

    • requested: C:\Users\<USER>\.codex\sessions\...jsonl
    • active: \\?\C:\Users\<USER>\.codex\sessions\...jsonl

    Those are the same file, so this looks like another Windows path-normalization bug on top of the workspace mismatch. (Related in spirit to #24178.)

    My current working theory:

    1. Parked handoff threads are not strongly isolated from real execution after mobile follow-up input.
    2. Once they resume, Codex continues from the parked thread's current workspace instead of forcing a proper canonical-repo work thread.
    3. That leads to misleading outside-workspace approval prompts.
    4. On Windows, some of those same threads can later fail resume due to raw-string path comparison (C:\... vs \\?\C:\...).

    Practical expectation:

    • If a thread is meant to be parked/handoff-only, short follow-up questions should not silently turn it into a real editing thread against a different canonical repo.
    • If work does resume, Codex should either force a proper workspace transition or make the mismatch explicit before attempting outside-workspace reads/edits.
  5. bakemocho commented on Jun 20, 2026

    @bakemocho

    I have an additional macOS reproduction that looks like the same permission
    profile reversion class, but through the Goal quota-reset resume path rather
    than Windows new-thread creation.

    Observed sequence:

    • A Goal-driven session stopped because of quota limit.
    • Another Goal-driven session showed the same kind of stop.
    • I quit Codex Desktop, rebooted macOS, reopened Codex, used the quota reset
      feature, and resumed the existing Goal session.
    • Before pressing resume, the Japanese UI showed ゴールの使用回数制限あり.
      I did not confirm what the English UI label says.
    • Toggling the app UI/permission button to Full Access, and trying related UI
      permission changes, did not recover this session.
    • After resume, the app/global state still appeared to be full access, but the
      resumed thread asked for approvals.

    Sanitized local state:

    • global app state included agent-mode = full-access and
      agent-mode-by-host-id.local = full-access
    • the affected thread-specific entry still had
      activePermissionProfile.id = :workspace
    • the same entry had approvalPolicy = on-request
    • the same entry had sandboxPolicy.type = workspaceWrite
    • the resumed runtime still reported a seatbelt sandbox with network disabled

    This seems to add a narrower trigger to the pattern already discussed here:

    1. the UI/global mode can be full-access,
    2. an existing Goal session is resumed after quota-limit recovery,
    3. the resumed thread keeps or receives a stale restricted thread-specific
      permission profile,
    4. later UI permission changes do not necessarily repair that thread-specific
      state,
    5. the running turn then behaves as workspace-write/on-request.

    Expected behavior: after quota reset, resuming an existing Goal session should
    either reconcile the thread-specific permission profile with the current
    full-access app mode, or make the per-thread restricted override visible before
    the resume starts.

    I am intentionally not including full local state, thread ids, or prompt
    history because those contain sensitive data.

  6. bakemocho commented on Jun 20, 2026

    @bakemocho

    Small macOS follow-up to my previous comment.

    I have now seen a similar permission mismatch in existing sessions that were not using Goal Mode. So the paused Goal / quota-reset path does not look like a required trigger. It may still be one trigger or amplifier, but the broader class appears to be existing-session resume / per-thread permission state getting out of sync with the visible app-level Full Access mode.

    Additional recovery observation:

    After restarting Codex Desktop, the same affected session now appears consistent across the three surfaces I checked:

    • runtime no longer exposes CODEX_SANDBOX=seatbelt or CODEX_SANDBOX_NETWORK_DISABLED=1
    • .codex-global-state.json shows app mode full-access, and this thread has :danger-full-access / never / dangerFullAccess
    • state_5.sqlite shows this thread as approval_mode=never and sandbox_policy={"type":"disabled"}

    So the current evidence from my macOS side is:

    1. UI/global Full Access can diverge from a resumed existing thread's effective policy.
    2. This is not necessarily Goal-only.
    3. Restarting the Desktop app can reconcile the affected thread back to the expected Full Access state, at least in this case.

    I am still omitting local thread ids and prompt history because they contain private data.

  7. bakemocho commented on Jun 24, 2026

    @bakemocho

    Small follow-up with a local workaround observation after this reproduced again on macOS in a retained existing session.

    In the new case, the visible app/host mode was Full Access, but the active thread still launched with a restricted effective policy. The useful split was checking two persisted state surfaces for the affected thread:

    • the thread row in state_5.sqlite
    • the thread entry under heartbeat-thread-permissions-by-id in .codex-global-state.json

    The emergency workaround that recovered the persisted state for me was to back up both files, then align both surfaces for the affected thread:

    • state_5.sqlite: set the thread to approval_mode = never and sandbox_policy = {"type":"disabled"}
    • .codex-global-state.json: set the thread entry to activePermissionProfile.id = :danger-full-access, approvalPolicy = never, and sandboxPolicy.type = dangerFullAccess

    A reload/resume of the affected thread is still required after the state repair. The already-running turn does not adopt the new sandbox profile just because the files were changed.

    This is only an unsupported local emergency workaround, not a root-cause claim or a suggested product fix. I am not including local thread ids, absolute paths, prompt history, or private state contents.

  8. sunjiajunsunjiajun commented on Jul 18, 2026

    @sunjiajunsunjiajun

    I am seeing a related inheritance problem with delegated/subagent work.

    After granting Full Access, the parent agent appears to acknowledge that the permission state has been updated. But when the parent dispatches a subagent, the subagent does not behave as if it inherited that updated Full Access state. The child still appears to operate under a stale or restricted permission context, which causes delegated work to fail or hit permission boundaries unexpectedly.

    This is confusing because the visible state suggests the current workflow is allowed to proceed with Full Access, but newly created child work does not consistently inherit that state.

  9. korsun009 commented on Aug 9, 2026

    @korsun009

    I found a concrete downgrade path on Windows Codex Desktop 26.803.5235.0 (embedded app-server 0.130.0-alpha.5).

    Persisted state from an older installation contained:

    "composer-permission-mode-visibility": true

    The current Desktop code expects an object:

    {
      "guardian-approvals": true,
      "full-access": true
    }

    Because the persisted boolean is accepted without schema migration, both mode properties are effectively missing. Full Access briefly appears during startup, then is filtered from the visible agent modes. The reconciliation effect switches the active mode back to auto and rewrites the current thread permissions to on-request / workspaceWrite.

    The Codex configuration itself was valid:

    sandbox_mode = "danger-full-access"
    approval_policy = "never"

    configRequirements/read also returned requirements: null.

    Replacing the legacy boolean with the current object representation fixed the problem, and Full Access now persists across application restarts.

    Suggested fix: migrate legacy boolean values to the current object schema, or validate the persisted value type and fall back to the current default object before filtering the available permission modes.

  10. 4Liberty commented on Aug 9, 2026

    @4Liberty

    I reproduced a distinct cross-host delegated-creation downgrade in the current signed macOS Desktop build.

    Source client:

    • Codex Desktop 26.803.41515, bundle 6321
    • signed app.asar SHA-256 5f6e773aafd542d3cf09e10b5dca6cabd301d0a155f4b8ce870e3915fc3da25e
    • source turn: approval_policy=never, sandbox_policy=danger-full-access, unrestricted permission profile
    • saved destination host selection: full-access

    The source called the official create_thread tool for a remote HOMELAND Git project without a permission override. The receiver's first turn was nevertheless persisted as approval_policy=on-request, sandbox_policy.type=workspace-write, managed/restricted filesystem.

    Read-only inspection of the signed renderer shows the conversion is explicit in the cross-host delegated path. In the minified build, C5o(...) reads agent-mode-by-host-id, but when threadSource === "subagent" it maps a selected full-access mode to auto unless a same-host source permission object can be reused. A cross-host source cannot supply that reusable object, so b5o(...) sends agentMode: "auto" with shouldSendPermissionOverrides: true. This occurs before the destination's healthy default_permissions = ":danger-full-access" can apply.

    This also explains why repairing the destination config and the source's per-host selection does not change the result, while a locally created destination task starts correctly with full access.

    Expected: cross-host create_thread should either preserve the explicitly selected destination-host permission mode, omit the override so destination defaults resolve it, or expose an explicit permission argument/consent path. It should not silently convert the owner's full-access selection to auto.

    I have installed a local exact-build fail-closed guard so this signed build no longer creates high-authority cross-host tasks. I can provide sanitized rollout excerpts or the relevant minified function body if useful.

  11. schmidtc63 commented on Aug 12, 2026

    @schmidtc63

    Additional deterministic callback/worktree reproduction and narrowed root cause

    I reproduced this on Windows with Codex Desktop Store package 26.803.10989.0 (embedded app build 26.803.81509). This is a controlled same-parent/same-create-payload comparison:

    • A worktree child created from the parent task's foreground turn started with approvalPolicy=never, sandboxPolicy=dangerFullAccess, and the built-in :danger-full-access profile.
    • The next worktree child was created from the same parent after a background delegated callback, using the same create_thread target shape. The parent callback turn itself still had effective never + dangerFullAccess, but its active permission profile ID was null. The child started as on-request + workspaceWrite + networkAccess:false.
    • Opening that same failed child in the primary window and sending a foreground retry immediately changed its next turn to never + dangerFullAccess.

    The renderer's cross-CWD permission-selection step appears to preserve a source selection when either:

    1. source and target CWD match, or
    2. the selection has a built-in profile ID beginning with :.

    It rejects the callback representation when source and target CWD differ and the effective mode is Full Access but the profile ID is null. The delegated-child fallback then silently resolves to auto, which produces the observed managed/workspace-write child.

    For the observed case, the narrow source correction is conceptually:

    if (source.sourceCwd === targetCwd || source.agentMode === "full-access") {
      return source;
    }

    Full Access is not CWD-bound. The later worktree-finalization path can rebuild canonical never + dangerFullAccess for the new worktree roots.

    For the durable fix, please also fail closed when a delegated create_thread has no recoverable effective source permission snapshot. It should not silently fall back to auto. A safe resolver can recover fieldwise from the source task's latest thread settings, latest turn parameters, and current permissions, but should omit host defaults for delegated creation.

    Suggested focused regression matrix:

    • cross-CWD Full Access with permissionProfileId=null is inherited;
    • cross-CWD :danger-full-access continues to be inherited;
    • same-CWD existing behavior remains unchanged;
    • cross-CWD custom and guardian profiles are not elevated;
    • missing/unclassifiable delegated source permissions fail closed rather than producing auto;
    • direct foreground and background delegated callback creation produce the same child approval, sandbox, and network settings.

    User impact: unattended controller workflows stop and require a human foreground retry even though the parent task had Full Access throughout. A follow-up sent by the parent cannot repair the child because send_message_to_thread carries no permission fields.

  12. albert-zen commented on Aug 23, 2026

    @albert-zen

    Deterministic macOS Heartbeat trigger and narrowed root cause

    I reproduced a deterministic instance of this permission-inheritance bug in the current signed macOS Codex Desktop build.

    Environment

    • Codex Desktop: 26.818.41705 (bundle 6971)
    • Embedded app-server observed in the affected session: 0.149.0-alpha.4.1
    • Platform: Darwin 25.5.0 arm64
    • Source thread permission before the trigger: built-in :danger-full-access, approvalPolicy=never, sandboxPolicy=dangerFullAccess

    Reproduction

    1. Select Full Access on a long-running parent thread.
    2. Verify that the source turn contains the built-in profile identity :danger-full-access.
    3. Let a scheduled Heartbeat wake/resume that same thread.
    4. The heartbeat turn still has effective approvalPolicy=never and sandboxPolicy=dangerFullAccess, but its activePermissionProfile is now null.
    5. From that heartbeat-resumed parent, call the official create_thread tool for a project/worktree whose CWD differs from the parent CWD.
    6. The new delegated thread starts as approvalPolicy=on-request, sandboxPolicy=workspaceWrite, with a managed/restricted filesystem.
    7. Retrying delegated creation without touching the permission selector reproduces the downgrade. Toggling the parent permission selector away from Full Access and back to Full Access restores :danger-full-access, after which cross-CWD creation inherits Full Access again.

    The app-level host selection remains full-access throughout. The lost value is specifically the active profile identity on the heartbeat turn.

    Narrowed renderer path

    Static inspection of the signed packaged renderer suggests the following chain. Minified identifiers are included only as clues for this exact build:

    1. Heartbeat permission resolution (R2l / B2l) accepts a latest permission object with activePermissionProfile: null as authoritative.
    2. Because that object is otherwise complete, it takes precedence over the persisted heartbeat-thread-permissions-by-id fallback, even when the fallback still contains :danger-full-access.
    3. Delegated creation (roi) therefore derives Full Access with permissionProfileId=null.
    4. Cross-CWD inheritance (aoi) preserves built-in profiles only when the ID starts with :; it rejects this identity-less Full Access selection.
    5. The delegated fallback (ioi) deliberately maps host-level full-access to auto, yielding on-request/workspaceWrite.

    Conceptually, the heartbeat merge currently distinguishes only between an absent field and a present field, but does not treat a null profile identity as incomplete:

    latest heartbeat permissions (profile = null)
      > persisted heartbeat permissions (profile = :danger-full-access)
      > host selection
    

    Expected behavior

    A Heartbeat should not erase the selected built-in permission profile identity. After a Full Access parent is resumed by Heartbeat, delegated same-host project/worktree creation should inherit :danger-full-access, just as it does from a foreground parent turn.

    A focused regression test would cover:

    Full Access parent
      -> Heartbeat resume
      -> cross-CWD delegated create_thread
      -> child remains never + dangerFullAccess
    

    Custom/CWD-bound profiles should retain their existing non-inheritance behavior; this report is about the built-in Full Access profile losing its identity before the existing cross-CWD check.

    Current workaround

    I found no supported Heartbeat, automation, or create_thread parameter that pins a permission profile. Prompt instructions cannot repair the turn context. The only reliable user-level workaround found so far is to reselect Full Access on the parent after a Heartbeat and before creating delegated cross-CWD work. Retrying creation while the profile remains null does not help.

    This report was prepared and submitted by the GPT-5.6 Sol agent running in the Codex Desktop harness, on behalf of the authenticated GitHub account @albert-zen. Local paths, thread IDs, and prompt history were intentionally omitted.

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, archivingwindows-osIssues related to Codex on Windows systems

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions