Skip to content

[macOS][regression] codex_app MCP override makes CODEX_APP_SERVER_USE_LOCAL_DAEMON=1 unreachable #41014

Open

Description

@johnnyeven

Summary

After updating ChatGPT Desktop, the existing managed local App Server path no longer works in a normal Desktop startup.

CODEX_APP_SERVER_USE_LOCAL_DAEMON=1 is present in the Desktop process environment and the managed daemon is healthy, but Desktop starts a private stdio app-server instead of attaching to the daemon's Unix socket. If another local client is already using the same thread through the managed daemon, Desktop then shows:

This is open in another app
Close it there to continue here.

This appears to be a startup-logic regression caused by the new app-managed codex_app MCP override.

Environment

  • ChatGPT Desktop: 26.820.60940 (build 7119)
  • Bundled Codex: codex-cli 0.150.0-alpha.8
  • Standalone managed Codex/App Server: 0.149.1
  • Bundled codex-app-tools plugin: 0.1.3
  • macOS: 26.5.2 (25F84)
  • Architecture: Apple Silicon / arm64

Managed daemon status before launching Desktop:

{
  "status": "running",
  "backend": "pid",
  "managedCodexVersion": "0.149.1",
  "socketPath": "$CODEX_HOME/app-server-control/app-server-control.sock",
  "cliVersion": "0.150.0-alpha.8",
  "appServerVersion": "0.149.1"
}

Previously working behavior

Before the Desktop update, this setup worked:

ChatGPT Desktop ────────┐
                        ├─ managed app-server --listen unix://
local sidecar/client ───┘

Both clients connected to the same Unix socket and could observe/continue the same live thread.

This is the same shared-daemon topology documented in #31991.

Steps to reproduce

  1. Install the standalone Codex package.

  2. Start the managed App Server:

    codex app-server daemon bootstrap
  3. Ensure the Desktop process receives:

    launchctl setenv CODEX_APP_SERVER_USE_LOCAL_DAEMON 1
  4. Fully quit and restart ChatGPT Desktop.

  5. Connect a second local client to $CODEX_HOME/app-server-control/app-server-control.sock.

  6. Open or continue a thread through that client.

  7. Open the same thread in ChatGPT Desktop.

Actual behavior

Desktop does not attach to the managed daemon. It uses a private stdio app-server, so the system has two app-server processes with separate writer ownership:

ChatGPT Desktop ── private app-server --listen stdio://
local client ───── managed app-server --listen unix://

Opening the managed-daemon thread in Desktop produces the active-writer / “open in another app” conflict.

Fully quitting Desktop releases its private writer, but that defeats the shared-client workflow.

Diagnostic evidence

Inspection of the packaged Desktop startup logic shows that the managed-daemon branch is selected only when all of the following are true, including:

configOverrides.length === 0 &&
process.env.CODEX_APP_SERVER_USE_LOCAL_DAEMON === "1"

The same startup now creates the Codex app-tools native pipe, sets CODEX_APP_TOOLS_PIPE_PATH, reads the bundled codex-app-tools/desktop-mcp.json, and returns a request-level override for:

mcp_servers.codex_app

Therefore, on a healthy normal Desktop startup:

codex_app native pipe starts
  → mcp_servers.codex_app override is non-empty
  → configOverrides.length !== 0
  → managed-daemon branch is skipped
  → private stdio app-server is spawned

The daemon version gate is not the blocker: the packaged minimum is older than 0.149.1, so this execution path is rejected before daemon version selection matters.

The current Desktop process environment was also checked:

CODEX_APP_SERVER_USE_LOCAL_DAEMON=set
CODEX_APP_TOOLS_PIPE_PATH=set
CODEX_CLI_PATH=unset
CODEX_APP_SERVER_FORCE_CLI=unset
CODEX_APP_SERVER_WS_URL=unset

Expected behavior

If CODEX_APP_SERVER_USE_LOCAL_DAEMON=1 is set and a compatible daemon is healthy, Desktop should attach to it.

At minimum, one of these should be supported:

  1. Give the explicit local-daemon setting precedence and disable Desktop-only app tools with a clear warning.
  2. Provide a supported setting to disable the app-managed codex_app MCP injection.
  3. Make the codex_app tools client/session-scoped so they do not require a process-wide app-server config override.
  4. Support per-connection config overlays on the managed daemon.

Why this is distinct from a generic writer-lock issue

The writer conflict is the visible symptom. The earlier transport-selection decision is the regression: an automatically injected Desktop MCP override makes the existing local-daemon branch unreachable during normal startup.

Related reports cover the individual symptoms, but not this combined cause:

Additional note

I understand that CODEX_APP_SERVER_USE_LOCAL_DAEMON is undocumented. However, the current Desktop bundle still contains the environment-variable check, daemon discovery/version gate, and Unix-socket transport path. The automatic codex_app override now prevents that retained path from being selected.

No thread IDs, usernames, repository names, or private paths are included.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    app-serverIssues involving app server protocol or interfaces
    mcpIssues related to the use of model context protocol (MCP) servers
    on Aug 27, 2026
  2. github-actions commented on Aug 27, 2026

    @github-actions
    Contributor

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

    Powered by Codex Action

  3. Zollicoff commented on Aug 31, 2026

    @Zollicoff

    Confirmed this still reproduces on the current macOS Desktop/SSH stack, with the same root cause described here.

    Current environment

    • ChatGPT/Codex Desktop: 26.825.51511 (build 7377)
    • Desktop-bundled Codex: 0.151.0-alpha.7.2
    • Managed standalone Codex/App Server used by Remote SSH: 0.151.0
    • macOS: Apple Silicon

    Exact topology

    The local Desktop starts its private bundled server:

    .../Contents/Resources/codex
      -c features.code_mode_host=true
      app-server
      --analytics-default-enabled
      -c mcp_servers.codex_app={...}
    

    The Desktop's saved SSH connection independently bootstraps the managed server:

    codex -c features.code_mode_host=true app-server --listen unix://
    

    Both processes use the same Codex home and thread store.

    Reproduction and lock ownership

    1. Open/resume a thread in the host's local Desktop.
    2. Open the same host through Remote SSH from a second Mac.
    3. Attempt to open the same thread remotely.
    4. Remote thread/resume repeatedly fails with:
    thread-store conflict: thread <redacted> already has an active writer
    

    lsof confirms the Desktop-bundled app-server holds the corresponding thread-writer-locks/<redacted>.lock file.

    The direction is symmetric. After the local Desktop releases ownership and the SSH app-server resumes the thread, lsof shows the SSH app-server holding that same lock; the local Desktop then becomes the side that cannot resume it.

    This was reproduced on two different remote hosts. No CODEX_HOME, CODEX_CLI_PATH, CODEX_APP_SERVER_WS_URL, CODEX_APP_SERVER_FORCE_CLI, or CODEX_SSH_SKIP_APP_SERVER_BOOT override is required.

    Packaged-code confirmation

    The current Desktop bundle still contains the local-daemon transport path guarded by CODEX_APP_SERVER_USE_LOCAL_DAEMON=1. It also still requires the request-level config override list to be empty before selecting that path.

    A normal Desktop launch injects both features.code_mode_host=true and the app-managed mcp_servers.codex_app override. Consequently, the override list is non-empty and Desktop selects a private stdio app-server instead of the healthy managed daemon socket.

    The managed daemon itself is healthy and version-compatible:

    {
      "status": "running",
      "managedCodexVersion": "0.151.0",
      "cliVersion": "0.151.0-alpha.7.2",
      "appServerVersion": "0.151.0"
    }

    Impact

    This breaks the intended host workflow:

    Codex Desktop open on remote Mac
    + Codex Desktop on another Mac connected to that host over SSH
    + same thread
    

    The thread can be listed and its metadata can be discovered, but only the app-server that currently owns the writer lock can resume and operate it. Fully quitting one surface merely transfers which side can work; it does not provide shared access.

    The exclusive writer lock appears to be functioning correctly. The missing piece is either:

    1. making codex_app tools connection/session-scoped so Desktop can share the managed daemon;
    2. supporting per-connection overlays on the daemon; or
    3. providing an explicit read-only follower plus safe ownership-handoff path across app-server processes.

    No thread IDs, hostnames, usernames, repository names, or private filesystem paths are included.

  4. beifangzhishi-ops commented on Sep 1, 2026

    @beifangzhishi-ops

    I can reproduce a closely related codex_app / shared App Server failure on Windows, but through a different transport path: an externally launched/shared Codex App Server over WebSocket rather than the managed local-daemon path described here.

    My setup is:

    codex.exe app-server --listen ws://127.0.0.1:45789
    

    and Codex Desktop is pointed at it with:

    CODEX_APP_SERVER_WS_URL=ws://127.0.0.1:45789
    

    In that topology, Desktop does connect to the external/shared App Server, but both thread/start and thread/resume fail with:

    invalid transport in mcp_servers.codex_app
    

    Important observations:

    So this appears related to the same broader design boundary described in this issue: Desktop-side runtime injection of mcp_servers.codex_app seems to assume an App Server lifecycle/transport managed directly by Desktop, and shared/external App Server topologies can break when that assumption is not true.

    The visible failure differs from #41014 — here the shared WebSocket server is selected, but the request-level codex_app configuration becomes invalid — while #41014 shows the override preventing the shared daemon path from being selected at all. That makes these look like two manifestations of the same codex_app runtime-injection architecture problem across different shared App Server transports.

  5. ScriptedAlchemy commented on Sep 15, 2026

    @ScriptedAlchemy

    Additional reproduction notes (2026-09-15)

    Observed on both Mac Studio (ChatGPT Desktop / Codex Framework ~152.x, codex-cli 0.154.0) and ubuntu-main:

    • Desktop launches codex app-server with plugins.codex-app-tools@openai-bundled.mcp_servers.codex_app.enabled=true and CODEX_APP_TOOLS_PIPE_PATH=/tmp/codex-browser-use/<uuid>.sock.
    • Shared control socket ~/.codex/app-server-control/app-server-control.sock is absent while Desktop is running (private stdio app-server), so external clients cannot turn/start into live Desktop threads via the documented unix transport.
    • Built-in cross-task UX ("Sent by ChatGPT from another task") goes through Desktop MCP tool send_message_to_thread on that temp pipe — not a public app-server RPC.

    We are tracking a Grok Bot ↔ Codex bridge that needs shared-daemon attach for Grok→Desktop inject:

    Happy to provide more process/env dumps if useful.

  6. fistashka05 commented on Sep 15, 2026

    @fistashka05

    Still reproducible on ChatGPT Desktop 26.908.40834.

    I independently reproduced the managed-daemon attachment regression on macOS.

    What I verified:

    • a standalone managed Codex/App Server 0.154.0 starts successfully;
    • its Unix control socket is created and accepts a WebSocket initialize;
    • CODEX_APP_SERVER_USE_LOCAL_DAEMON=1 is set before a full ChatGPT Desktop restart and is inherited by the restarted Desktop process;
    • Desktop nevertheless launches a separate private stdio app-server;
    • the Desktop thread is not visible through the managed daemon.

    Observed topology:

    ChatGPT Desktop -> private stdio app-server
    local control client -> managed Unix-socket app-server
    

    Expected topology:

    ChatGPT Desktop + local control client -> same managed Unix-socket app-server
    

    I also tested the MCP side independently. Dynamic external MCP operation itself works when driven through the Desktop App Server: config/mcpServer/reload, thread-scoped MCP status, model-visible tool mount, one real model-initiated tools/call, disable/reload, and cleanup all succeeded with no leak.

    The remaining blocker is specifically the Desktop attachment/control-plane boundary: a UserPromptSubmit hook or local sidecar cannot reach the UI-owned private App Server for same-turn MCP activation through the supported managed-daemon socket because Desktop does not attach to that daemon.

    No unsupported process/FD injection, app-bundle patching, CODEX_CLI_PATH override, or manual MCP proxy was used. Test state was restored after reproduction.

    This appears to confirm that the codex_app override / private-stdio selection regression described in #41014 is still present in Desktop 26.908.40834.

  7. Pvxlabs commented on Oct 11, 2026

    @Pvxlabs

    Current macOS reproduction and read-only runtime observation requirement

    Adding sanitized evidence to this existing issue rather than opening a duplicate. The transport-selection root cause matches #41014. The narrower requirement below is independent, read-only observation of an existing Desktop-owned turn while retaining plugins; it does not require an external client to resume or mutate the thread.

    Component and environment

    Codex Desktop on macOS 26.6.2, arm64.
    Desktop version: 26.1007.30912, build 20156.
    Bundled CLI/app-server: 0.162.0-alpha.17.2.
    A separately configured managed daemon uses 0.162.1.

    Problem

    An independent read-only monitoring application cannot obtain authoritative current status for an already-running Desktop-owned thread when Desktop uses a STDIO Owner with plugin configuration overrides.

    Observed for the same exact thread T and current turn R:

    • Desktop's trusted task tool reports thread status active, turn status inProgress and the expected cwd.
    • Read-only thread/read and thread/turns/list against the separately configured shared daemon return thread status notLoaded and turn status interrupted for T/R.
    • That daemon is not the loaded runtime Owner. Successful transport and shared persisted history do not establish ownership.
    • The Desktop-owned process has anonymous STDIO connections; no external named app-server listener was observed.
    • Setting CODEX_APP_SERVER_USE_LOCAL_DAEMON=1 did not move the Desktop session to the daemon in this observed configuration. Prior installed-source inspection found that the shared-daemon selection requires absent configuration overrides, while Desktop supplies plugin overrides.

    The monitor therefore correctly keeps this session Unknown instead of claiming Running. Reading historical RUNNING fields or repeating a historical status with a new timestamp would manufacture liveness.

    Request

    Please confirm whether a supported independent, authenticated, read-only Desktop Owner status API already exists. If not, expose one that works for existing STDIO-owned Desktop sessions without disabling plugins, changing Desktop binaries, restarting active sessions, or taking over the thread.

    Required minimum capabilities:

    1. Exact thread ID, current/requested turn ID, cwd, runtime thread status and exact turn status.
    2. Fresh Owner observation timestamp, Owner instance/connection generation, and source started/completed timestamps.
    3. Queryable current state, with exact-turn completed/failed/interrupted outcomes. Events alone must not imply indefinite liveness.
    4. Independent read-only authorization restricted to the local user and approved projects. No conversation bodies or tool outputs are needed for the status endpoint.
    5. Explicit rejection of start, resume, steer, interrupt, fork, archive/delete and tool-response operations through this capability. Read must not create a competing Owner, change plugin settings or alter thread lifecycle.
    6. Documented capability/version discovery, stale/unavailable semantics and safe reconnect behavior.

    An official local-only transport or SDK export is sufficient. We are not requesting access to an executor's private injected App Tools pipe or caller credentials, and are not proposing Desktop stdout interception.

    Reproduction without publishing private session data

    1. Keep normal Desktop plugins enabled and start a harmless bounded task in an isolated project.
    2. Record Desktop's exact thread ID T and current turn ID R privately.
    3. From the separately configured shared-daemon endpoint, initialize a read-only client and request thread/read(T) and thread/turns/list(T) for R. Do not resume or start T on this daemon.
    4. Compare Desktop's actual loaded runtime status with the daemon projection for the same identity and cwd.
    5. The observed mismatch is active/inProgress vs notLoaded/interrupted; an external monitor cannot establish the actual Owner through the available configured endpoint.

    This report documents a specific installed configuration, not a claim that all Desktop builds select STDIO whenever any override exists.

    Acceptance criteria for a complete solution

    • A separate collector can query the actual Desktop Owner with plugins still enabled, without private caller-context reuse or lifecycle mutation.
    • A real task yields exact T/R active + inProgress with fresh source evidence. The installed monitor's list, detail and Active membership agree on Running.
    • Wrong turn/cwd, replay, conflicting Owner and disconnection cannot yield valid Running; old evidence expires without synthetic heartbeat refresh.
    • The same task finishes naturally. Exact T/R reports completed with source completedAt. List, detail, phase, duration and Completed/Active membership agree.
    • Failed and interrupted outcomes remain distinct; native completion does not invent a canonical business PASS.
    • Refresh, reselection and window reopen preserve terminal state; a later GET of an older Running snapshot cannot overwrite the exact terminal.
    • Existing canonical completion, execution protocol, historical records and unrelated executions remain unchanged.

    Privacy

    This report intentionally omits real thread/turn IDs, cwd/socket paths, project names, private logs, credentials, session transcripts and screenshots. Additional diagnostics should only be shared through an explicitly authorized channel after review.

  8. nilswx commented on Oct 14, 2026

    @nilswx

    Current macOS confirmation (2026-10-14)

    I reproduced the same ownership split with the current installed CLI/app-server 0.162.1:

    • ChatGPT Desktop starts its bundled Codex app-server as a private stdio child.
    • codex agents starts/uses the separate Machine-managed Unix-socket daemon for the same .codex home.
    • The Desktop-owned thread is listed from persisted state but opens as a read-only snapshot in the separate daemon because the Desktop app-server retains the active writer.
    • lsof shows anonymous socketpairs for the Desktop child, no TCP listener, and a separate named socket for the managed daemon.

    No process was restarted or signaled, and no writer lock was altered. This confirms the reported external-client limitation still affects the current CLI generation; the supported Desktop owner endpoint / daemon-attachment path remains the missing capability.

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 appapp-serverIssues involving app server protocol or interfacesbugSomething isn't workingmcpIssues related to the use of model context protocol (MCP) servers

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions