Desktop: full-CDP silently disabled when CODEX_HOME is a symlink

Open 💬 1 comment Opened Jul 25, 2026 by 0xdevalias

Summary

Codex Desktop's Browser Developer Mode can show Enable full CDP access as enabled while silently omitting the cdp tab capability from both Browser backends when CODEX_HOME (~/.codex) is a symlink.

The trusted Browser runtime does receive the privileged nodeRepl.config bridge. However, its call to readToml("browser/config.toml") rejects with:

Codex TOML directory must not be a symlink

The Browser client catches that exception, replaces it with a generic internal disabled result, and omits the cdp capability. The settings UI continues to display the toggle as enabled because it reads the same config through a different path that succeeds.

Environment

  • Codex Desktop: 26.715.71837 (CFBundleVersion 5702)
  • Bundle ID: com.openai.codex
  • Bundled CLI: codex-cli 0.145.0-alpha.30
  • Platform: Darwin 25.3.0 arm64 arm / macOS 26.3 (25D125)
  • Browser bundled plugin: 26.715.71837
  • codex features list reports both in_app_browser and browser_use_full_cdp_access as stable/true

Reproduction

  1. Use a symlink for CODEX_HOME. In this reproduction:

``text
~/.codex -> ~/.dotfiles/codex/codex.symlink
``

  1. In Codex Settings → Browser, enable Developer Mode / Enable full CDP access.
  2. Confirm ~/.codex/browser/config.toml contains:

``toml
full_cdp_access_enabled = true
``

  1. Restart/reinitialize Browser Use and inspect the capabilities returned by agent.browsers.list().

Actual behavior

Both healthy, connected backends omit the tab-level cdp capability:

  • Chrome (extension): browser capability viewport; tab capability pageAssets only.
  • Codex In-app Browser (iab): browser capabilities visibility, viewport; tab capability pageAssets only.

The settings UI still shows full CDP access as enabled. No actionable error is shown, and codex doctor does not identify the rejected configuration.

Expected behavior

  • A symlinked CODEX_HOME should work, provided the resolved path remains within the intended user-controlled configuration directory and passes appropriate ownership/permission validation.
  • If symlinks truly cannot be supported safely, the settings UI should refuse the change or show a clear diagnostic instead of displaying an enabled state that the Browser runtime will silently reject.
  • codex doctor should report this configuration/runtime mismatch and include the underlying rejection reason.

Live diagnosis

I attached a temporary in-process debugger to the active Node REPL runtime and set breakpoints inside the trusted, hash-approved bundled Browser client.

The full-CDP gate calls these privileged methods in parallel:

nodeRepl.config.readToml("browser/config.toml")
nodeRepl.config.readRequirements()

Observed results:

[
  {
    "status": "rejected",
    "reason": {
      "name": "Error",
      "message": "Codex TOML directory must not be a symlink"
    }
  },
  {
    "status": "fulfilled",
    "value": {
      "requirements": null
    }
  }
]

This establishes that:

  1. The privileged bridge is present in trusted Browser code.
  2. Enterprise requirements are not disabling the feature.
  3. The TOML read fails specifically because ~/.codex is a symlink.
  4. The Browser client catches the exception and returns:

``text
Full CDP access could not be verified from browser config and enterprise policy.
``

  1. Capability enrichment consequently omits cdp.

The public/model-facing globalThis.nodeRepl intentionally lacks config, so checking globalThis.nodeRepl?.config from ordinary submitted code is not a valid test for whether the trusted privileged bridge exists.

UX and diagnostic problems

  • The UI reports the persisted setting, not whether the runtime can actually use it.
  • The actionable native error is swallowed and replaced with a generic disabled state.
  • A normal developer configuration layout is rejected without documentation or a diagnostic.
  • There is no codex doctor check for a symlinked CODEX_HOME or for an enabled-but-unavailable full-CDP capability.

Relevant evidence

| Artifact | SHA-256 |
| --- | --- |
| /Applications/ChatGPT.app/Contents/Resources/app.asar | 11292b6a04d8aef36c30940b94ce3a744844dc5a52797228fbebb87f8529f102 |
| /Applications/ChatGPT.app/Contents/Resources/cua_node/bin/node_repl | 280df9eed10a975c7d5db7ee418b7da4df6de79158968183d2572e78bac746d2 |
| bundled browser-client.mjs | 2793c6809f8a4c90923435aa3a6d1e6f8cabb145212f06328b860b076b899525 |

The exact symlink rejection string is present in the shipped native node_repl executable. I could not find that implementation or either exact error string in the public openai/codex source tree.

Related issues

  • #24017 reports persisted Browser approvals being ignored and inferred that nodeRepl.config was missing.
  • #30947 reports Browser policy failures and similarly inferred that the privileged bridge was missing.

Those issues may have different platform-specific causes. This report does not claim the symlink rejection explains either one, but the restricted-versus-privileged nodeRepl distinction and swallowed readToml exception may provide a deeper diagnostic path.

View original on GitHub ↗

1 Comment

0xdevalias · 1 month ago

I also submitted this through the Codex Desktop in-app feedback flow.

In-app feedback ID: 019f9755-e508-7d62-ae34-e7162c0f58b5