Skip to content

[Bug]: Codex ChatGPT connection fails on Windows without Developer Mode: shadow home symlinks fail with EPERM #14325

Open

Description

@braziliansalsa

What happened

I connected Codex through the new "Connect with ChatGPT" flow. Sign-in succeeded, but the provider showed "Could not check Codex right now. Retry, or reconnect in provider settings." Retrying and reconnecting didn't help. Turning on Windows Developer Mode and restarting T3 Code fixed it. After I turned Developer Mode off, it kept working until I updated the ChatGPT desktop app. Then the same error came back, and I had to turn Developer Mode on again.

Diagnosis

materializeCodexShadowHome links each entry of the shared Codex home (~/.codex) into the managed provider's shadow home through ensureSymlink (apps/server/src/provider/Drivers/CodexHomeLayout.ts:216). That function calls fileSystem.symlink(target, link) with no link type. On Windows, a normal (non-elevated) process can't create symbolic links unless Developer Mode is on, so every attempt fails with EPERM and the provider status check fails.

The failure keeps coming back because the list of entries is built from readDirectory(sharedHomePath) on every refresh (CodexHomeLayout.ts:361-378). That includes transient files the Codex/ChatGPT desktop app writes, such as ..codex-global-state.json.bak.tmp-<ts>-<uuid>. Each new temporary file needs a fresh link, so turning Developer Mode on only once isn't enough.

The codebase already works around this elsewhere. apps/server/src/provider/antigravityAuthSupport.ts:288 uses "junction" on win32 so it can link without elevation. Possible fixes:

  • Use junctions for directories on win32.
  • Use hard links or copies for files, since junctions only work for directories.
  • Skip transient *.tmp-* entries.
  • Treat a failure to link an unknown entry as non-fatal instead of failing the whole provider check.

Steps to reproduce

  1. On Windows 11 with Developer Mode off and a standard (non-admin) user, run the T3 Code desktop app.
  2. Go to Settings → Providers → Connect with ChatGPT, and finish the OpenAI sign-in.
  3. The provider shows "Could not check Codex right now." The server trace shows CodexHomeLayout.ensureSymlink failing with EPERM.
  4. Turn Developer Mode on and restart. The provider becomes ready.
  5. Turn Developer Mode off, then use or update the ChatGPT desktop app so it writes new temporary files in ~/.codex. The error comes back.

Version

0.0.44 (451afcb)

Environment

Windows 11 Home 10.0.26200 x64, desktop app with local server, Node v26.8.2, codex-cli 0.159.1

Evidence

CodexShadowHomeFileSystemError: Codex shadow home filesystem operation 'symlink' failed for '~\.t3\userdata\providers\codex\codex_<id>\shadow\sessions' to '~\.codex\sessions'.
    at CodexHomeLayout.ensureSymlink (.../server/dist/bin.mjs:154178:10)
    at materializeCodexShadowHome (.../server/dist/bin.mjs:154260:11)
    at refreshSnapshot (.../server/dist/bin.mjs:127336:12)
  [cause]: PlatformError: Unknown: FileSystem.symlink
    [cause]: Error: EPERM: operation not permitted, symlink '~\.codex\sessions' -> '~\.t3\userdata\providers\codex\codex_<id>\shadow\sessions'

# After the ChatGPT desktop app update (Developer Mode off):
Failure  ..codex-global-state.json.bak.tmp-1790718936714-<uuid>   (x4)

# Before this, CodexChatGptAuth.authenticate -> Success

Related issues

#14321 is about the same ChatGPT connection, but it covers missing models, not this provider-check failure. Not a duplicate.

Fix applied or workaround

The user turned on Windows Developer Mode (Settings → System → For developers). It has to stay on, because new temporary files in ~/.codex need new symlinks.

Filed by

Claude Code (Claude Opus 5.5) via t3 triage

Activity

  1. juliusmarminge commented on Sep 29, 2026

    @juliusmarminge
    Member

    Triage

    Confirmed on v0.0.44 (451afcb). Nothing after that commit touches this path, and there is no open PR.

    Managed ChatGPT instances use an auth-overlay shadow home (CodexManagedHome.ts: any instance id other than codex gets providers/codex/<id>/shadow). materializeCodexShadowHome then symlinks every non-private entry of ~/.codex into that directory, including the known directories it creates itself (sessions, sqlite, skills, and the rest). ensureSymlink calls Effect's FileSystem.symlink, which has no Windows link-type argument and ends up as fs.symlink with no type. On Windows that is a real symlink, so a normal user without Developer Mode gets EPERM. The status check catches that and replaces it with "Could not check Codex right now," which matches the trace (sessions → refreshSnapshot). The built-in CLI provider (instanceId === "codex") stays in direct mode and does not hit this.

    Developer Mode only has to stay on because the entry list is rebuilt from readDirectory on every refresh. A new file such as ..codex-global-state.json.bak.tmp-* is not in PRIVATE_ENTRY_NAMES or SHADOW_LOCAL_ENTRY_NAMES, so one unprivileged create fails the whole provider check. #14321 is the same sign-in flow, but it is the model catalog, not this.

    Junctions, as in antigravityAuthSupport.ts, cover directories only, and they have to go through node:fs because Effect cannot pass "junction". Files still need a link, and a hard link or copy is not a symlink: the next refresh's readLink returns EINVAL, readLinkState treats that as NotSymlink, and config.toml (anything outside mcp-oauth-locks) fails with CodexShadowHomeEntryConflictError. Skipping transient *.tmp-* names, or not failing the check when an unknown entry cannot be linked, is still required for the recurrence.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Sep 29, 2026
  3. changed the title [-]Codex ChatGPT connection fails on Windows without Developer Mode: shadow home symlinks fail with EPERM[/-] [+][BUG] Codex ChatGPT connection fails on Windows without Developer Mode: shadow home symlinks fail with EPERM[/+] on Sep 29, 2026
  4. changed the title [-][BUG] Codex ChatGPT connection fails on Windows without Developer Mode: shadow home symlinks fail with EPERM[/-] [+][Bug]: Codex ChatGPT connection fails on Windows without Developer Mode: shadow home symlinks fail with EPERM[/+] on Sep 29, 2026
  5. leonardoxr commented on Oct 6, 2026

    @leonardoxr

    Still reproduces on 0.0.46-nightly.20261005.2702 (Windows 11 Pro 10.0.26200, standard non-elevated user, codex-cli 0.160.1). The "ChatGPT - Personal" managed instance showed "Authenticated as … · ChatGPT · Could not check Codex right now." The server trace had the same CodexShadowHomeFileSystemError / EPERM on ~\.codex\sessions (14 times), and the shadow directory stayed empty.

    Notes for anyone else hitting this:

    • The Developer Mode toggle moved and may not persist. On 25H2 it's under Settings → System → Advanced → For developers (ms-settings:developers). On this machine, turning the toggle on did not create HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock at all, and symlink creation still failed. Setting the value directly from an elevated shell worked:
      reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v AllowDevelopmentWithoutDevLicense /d 1
      
      After restarting T3 Code, the shadow home was populated with symlinks, the EPERM errors stopped, and the provider check passed.
    • Workaround without Developer Mode: the built-in codex instance (direct mode, uses the CLI's own codex login) does not build a shadow home and works without symlink privileges.
    • To confirm you have this bug and not [Bug]: Codex provider is marked Unavailable  #14333, look for EPERM: operation not permitted, symlink in ~\.t3\userdata\logs\server.trace.ndjson.
  6. added 3 commits that reference this issue on Oct 18, 2026
    9fd9689
    618d5d5
    db8e8e0
  7. lord007tn commented on Oct 19, 2026

    @lord007tn

    I did enable Developer Mode in Windows
    and it's working properly

    If anyone here wants a workaround

  8. braziliansalsa commented on Oct 21, 2026

    @braziliansalsa
    Author

    Rechecked on 2026-10-26: still present in 0.1.2 (latest release), nightly 0.1.3-nightly.20261026.3717 and main (0712398). The link code is unchanged, and the regression tests in #22573 still fail against main.

    Following the close note on #15277, I opened a smaller fix: #22573. On Windows only, a directory falls back to a junction when Windows refuses the symlink, and an entry that cannot be linked is skipped instead of failing the check. It changes one source file plus tests. I verified it on Windows 11 with Developer Mode off, including Connect with ChatGPT and a thread running end to end; details are in the PR.

    One trade-off needs a maintainer call: without Developer Mode, top-level files such as config.toml and AGENTS.md are not shared into managed accounts.

    Written by Claude Code (Claude Opus 5.5) on behalf of @braziliansalsa, via t3 triage.

  9. juliusmarminge commented on Oct 22, 2026

    @juliusmarminge
    Member

    Fresh triage: this is a reported Windows compatibility bug, not a reported exploitable vulnerability.

    Inspected the server source on Linux at 07d38b8. CodexHomeLayout.ensureSymlink still calls FileSystem.symlink(target, link) without a Windows link type; materializeCodexShadowHome still rebuilds shared entries on refresh while keeping auth.json private. This is source inspection, not a Windows runtime reproduction: this host cannot run the reported Windows 11 desktop/Developer Mode sequence.

    Leaving this open for maintainer handling. Changing the managed account's credential-isolating shadow-home materialization falls under the triage flow's protected-surface stop. The existing proposed fix also needs a decision on which shared files remain available without Developer Mode.

  10. ViperJuice commented on Oct 29, 2026

    @ViperJuice

    Rechecked on 2026-10-29: this recurred on nightly 0.1.4-nightly.20261029.3853 via PR #26286 ("Codex shadow homes share thread writer locks with the main home", merged 2026-10-28T04:58:30Z), which added thread-writer-locks to KNOWN_SHARED_DIRECTORIES — the same ensureSymlink → fileSystem.symlink(target, link) path this issue already tracks, with no Windows link-type argument.

    Environment: Windows 11 Pro 10.0.26300 x64, standard (non-admin) user, T3 desktop app, codex-cli 0.165.0-alpha.1.2 bundled runtime.

    Observed error (T3 provider status UI):

    Driver 'codex' failed to create instance: Codex shadow home filesystem operation 'symlink' failed for 'C:\Users\<user>\.codex-t3\accounts\<account>\thread-writer-locks' to 'C:\Users\<user>\.codex-t3\shared\thread-writer-locks'.
    

    HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock\AllowDevelopmentWithoutDevLicense did not exist at all on this machine (Developer Mode had never been turned on). Creating it and enabling Developer Mode unblocked a fresh symlink creation immediately (verified in a new session without any T3/app restart), confirming this is the same EPERM-without-privilege cause already diagnosed here.

    Additional evidence for the proposed junction-fallback fix (#22573): on this exact shadow home, fsutil reparsepoint query shows every pre-existing shared entry (cache, logs, mcp-oauth-locks, sessions, etc.) is a directory junction (IO_REPARSE_TAG_MOUNT_POINT), while the new thread-writer-locks link that failed and was later created is a true NTFS symlink (IO_REPARSE_TAG_SYMLINK). A junction-on-Windows fallback for directory entries, as #22573 proposes, would have avoided this recurrence entirely.

    Workaround applied: enabled Windows Developer Mode; provider recovered.

  11. grenmango51 commented on Nov 6, 2026

    @grenmango51

    Confirming the same recurrence on 0.1.8-nightly.20261106.4262 (c55db3e9c7), Windows 11, standard non-admin user, Developer Mode never enabled.

    It is exactly the #26286 case, and nothing else. shadow\thread-writer-locks is the only entry that needed a link. The ten pre-existing shared entries (archived_sessions, cache, logs, mcp-oauth-locks, plugins, sessions, shell_snapshots, skills, sqlite, worktrees) are all already junctions on this machine (fsutil reparsepoint query → tag 0xa0000003), so they never exercised the privilege. Only the newly added thread-writer-locks went through the real-symlink path, and it failed every refresh:

    CodexShadowHomeFileSystemError: Codex shadow home filesystem operation 'symlink' failed for
      C:\Users\<user>\.t3\userdata\providers\codex\codex_<id>\shadow\thread-writer-locks
      to ...\shared-runtime\thread-writer-locks
    [cause]: PlatformError: PermissionDenied: FileSystem.symlink (...\shared-runtime\thread-writer-locks)
    

    182 failed materializeCodexShadowHome spans in the retained trace, from 14:27:30Z to 16:14:54Z, roughly one a minute for the whole window. The provider showed as unavailable and sessions launched during it failed to start.

    The file is unchanged on main. apps/server/src/provider/Drivers/CodexHomeLayout.ts at c55db3e9c7 is byte-identical to main at 6577c6b6 (sha256 A946F1940EC378B8002A9DFA2365081ACC01601D8E244523CD90D36D4A23C41B): ensureSymlink still calls FileSystem.symlink(target, link) with no link type, and thread-writer-locks is still in KNOWN_SHARED_DIRECTORIES. This is live on main today, not only in the nightlies that were reported.

    Repair, and what it says about #22573. Creating the missing link by hand recovered the provider on the next refresh:

    mklink /J ...\shadow\thread-writer-locks ...\shared-runtime\thread-writer-locks
    

    materializeCodexShadowHome succeeded at 16:15:54Z, with no restart, and has succeeded 16 times since (latest 16:31:45Z). fs.readlinkSync returns the absolute target T3 compares against, so a junction satisfies readLinkState and the target comparison here. That means the directory half of #22573 is sufficient for this entry; the "skip unlinkable entries" half is not needed for it, though it is still needed for top-level files such as config.toml.

    One asymmetry worth knowing for the fix: older builds made the pre-existing entries as junctions, this build tried a true symlink for the new one, so a single shadow home can end up with both link kinds. Both pass the readLink comparison — that is the state on disk here now, and nothing objects to it.

    The trigger is structural rather than a fluke, though: any future addition to KNOWN_SHARED_DIRECTORIES, or any refresh that has to link an entry the shared home did not have before, fails on an unprivileged Windows account until someone with admin rights creates the link by hand.

    Written with an AI assistant (OpenCode) on behalf of @grenmango51.

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

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions