Skip to content

Never run the Codex inside Codex.app or ChatGPT.app - #97

Open
jparker2006 wants to merge 1 commit into
davidondrej:mainfrom
jparker2006:fix-96-no-app-bundle-codex
Open

jparker2006 wants to merge 1 commit into
davidondrej:mainfrom
jparker2006:fix-96-no-app-bundle-codex

Conversation

@jparker2006

Copy link
Copy Markdown
Contributor

Problem. When there's no standalone Codex CLI, findCliExecutable("codex") fell back to the binary inside /Applications or ~/Applications {ChatGPT,Codex}.app/Contents/Resources/{codex-cli/bin/codex,codex}. Every caller then ran it: provider status (--version), codex login, codex app-server, codex mcp list, local threads, model list. When macOS flags that Codex build, running a binary inside the bundle makes macOS move the whole app to the Bin. That's what happened in #96. provider-maintenance.ts also treated an app-bundled Codex as installed and never offered Install or Update ("only updates with the app"), so users were stuck on whatever binary the app shipped.

Repro. I wrote two tests. Both fail on origin/main and pass with this PR. Each one stubs process.platform to darwin the same way apps/cli and apps/desktop tests do, and uses a temp HOME with Applications/Codex.app and ChatGPT.app inside it:

  • process-utils: on a Codex.app-only machine, findCliExecutable("codex") returns …/Applications/ChatGPT.app/Contents/Resources/codex-cli/bin/codex on main (expected '…/Ap…' to be null). A PATH symlink into the bundle is returned too.
  • provider-codex: the fake bundled codex writes a marker file when it runs. On main, getCodexProviderInstallationStatus() runs it (expected true to be false).

Fix. Cloudroom never runs binaries inside other apps' .app bundles.

  1. cli-locations.ts: removed codexAppBundlePaths. Codex lookup also skips any candidate whose realpath contains .app/, so a PATH symlink into Codex.app is skipped too. Official standalone, PATH, and Homebrew/npm installs resolve exactly as before.
  2. provider-maintenance.ts: removed the appBundled logic, which is now dead. If no allowed Codex exists, codexExecutable() returns the path OpenAI's installer would use instead of a bare codex. A bare name would let the OS's PATH lookup reach the same Codex.app symlink. Status, health and usage then report "not installed" without running anything, and a thread or model-list spawn fails with the usual "could not find the Codex CLI" guidance. I duplicated the 4-line path because officialCodexPath isn't exported through @get-bb/plugin-sdk.
  3. bridge.ts: the missing-CLI message now adds "Cloudroom never runs the copy inside Codex.app or ChatGPT.app."

The .app/ rule is Codex-only. Claude's own CLI can live in a claude.app bundle (see desktopClaude()), so a blanket rule in findCliExecutable could break Claude lookups.

Tests. I ran these from gui/ after npx --yes pnpm@9.15.9 install --frozen-lockfile:

  • cd packages/process-utils && npx vitest run --config vitest.config.ts → 5 files passed, 36 tests passed, 6 skipped (Windows-only)
  • cd plugins/provider-codex && npx vitest run --config vitest.config.ts --maxWorkers=2 → 28 files passed, 286 tests passed
  • npx turbo run typecheck --filter=@cloudroom/process-utils --filter=bb-plugin-provider-codex --concurrency=2 --force → 6/6 tasks passed
  • npx oxfmt --check on the touched files passes, except bridge.ts, which was already unformatted on main. I left it alone to avoid churn.
  • Clean-Mac check: my sandbox has its own /usr/local/bin/codex, so I renamed it for one run. Without it, both new tests fail on main as shown above and pass with this PR. That includes the installed: false + installAction.kind: "install" branch. Both tests compare against "whatever non-bundle codex this machine has", so they're hermetic on dev Macs and CI.

Where the UI triggers codex --version (routes.providerCliStatus). It fires on app launch, before any click, while setup step 1 is showing:

  • routes.providerCliStatus (apps/server/src/routes/hosts.ts:347) → getProviderInstallations → Codex bridge → getCodexProviderInstallationStatus → codex --version.
  • SidebarUpdatesBadge (AppSidebar.tsx:321) → useUpdateInventory (useUpdateInventory.ts:88) queries it for every connected host. <Onboarding /> is a sibling of <Routes> in App.tsx, so AppLayout and its sidebar mount under the setup overlay at launch.
  • RootComposeView.tsx:877 (home compose view) also queries it. So do ProvidersSettingsSection, the Updates settings, MachineSettingsView and MissingProviderCliRow when they're shown.
  • All of these use SESSION_STATIC_QUERY_POLICY (staleTime: Infinity), so it runs once per session per host.
  • runProviderAutoUpdates (start-server.ts:319, 60 s after start and then every 10 min) hits the same status, but it's opt-in (providerAutoUpdate defaults to []).

On v135, a Codex.app-only Mac therefore also ran Codex.app's codex --version from this path at launch, a third background trigger next to setup-telemetry and harness-versions. With this PR, that path can only run a CLI the user installed. I didn't redesign the UI.

Other code that builds paths into Codex.app or ChatGPT.app (I grepped all of gui/):

  • packages/scripts/src/commands/archive-codex-tmp-bb-sessions.ts is a manual dev script (pnpm codex:archive-tmp-bb-sessions) and nothing in the app calls it. Its default is $CODEX_BIN, then /Applications/Codex.app/Contents/Resources/codex, then codex on PATH, so it does default to the bundle when no flag is given. I left it unchanged because it never runs automatically.
  • apps/server/src/services/cloudroom/setup-telemetry.ts appVersion("Codex.app") only reads Info.plist with plutil and doesn't run the bundle. Its cliPlace "app_bundle" branch can no longer happen for Codex. (That's builder 2's file; I didn't edit it.)

For the other builders (not edited here):

  • sandboxes.ts:102,142 (builder 1) use findCliExecutable("codex") ?? "codex" for codex login and codex app-server. The bare "codex" fallback can still reach a PATH symlink into Codex.app. It needs the same fix as codexExecutable().
  • claude-token.ts runSetupToken runs the Claude desktop app's private …/Claude/claude-code/<v>/claude.app/Contents/MacOS/claude when no Claude CLI exists. It only runs when the user clicks Connect Claude, but it's literally a binary inside another app's bundle.

What users will notice. People whose only Codex is inside Codex.app or ChatGPT.app now see Codex as not installed. They'll be asked to Install the Codex CLI once, using the existing Install action: npm install -g @openai/codex, falling back to OpenAI's official installer. After that, Codex updates normally instead of being stuck on the app's copy. Everyone with a standalone, npm or Homebrew Codex sees no change.

Not verified. I couldn't test the real macOS side (XProtect moving a flagged app) in this Linux sandbox. All darwin branches were tested with a stubbed process.platform.

Refs #96

Cloudroom fell back to the codex binary inside Codex.app or ChatGPT.app and
ran it, including in the background. When macOS flags that build, it moves
the whole app to the Bin (davidondrej#96). Codex lookup now skips app bundles, even
through a PATH symlink; those users get the existing Install action.
jparker2006 added a commit to jparker2006/cloudroom-core that referenced this pull request Oct 31, 2026
With davidondrej#97 a null lookup means no usable Codex; a bare "codex" could still
reach one inside Codex.app through the OS PATH lookup. Sign-in now fails with
the not-installed error, and the expiry check counts it as signed in.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant