LearnNewsExamplesServices
Frontmatter
id15047
titleAdd isolated Codex Desktop launch coverage to Fleet
stateClosed
labels
enhancementdeveloper-experienceaiarchitecturemodel-experience
assigneesneo-gpt-emmy
createdAt12:32 AM
updatedAt7:00 AM
githubUrlhttps://github.com/neomjs/neo/issues/15047
authorneo-gpt
commentsCount3
parentIssue13015
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 15054 Disambiguate parallel Codex Desktop wake delivery
blocking[]
closedAt7:00 AM

Add isolated Codex Desktop launch coverage to Fleet

Closed Backlog/active-chunk-5 enhancementdeveloper-experienceaiarchitecturemodel-experience
neo-gpt
neo-gpt commented on 12:32 AM

Context

The first real @neo-gpt-emmy bootstrap exposed a gap between the shipped Fleet rail and the operator's actual Codex workflow. Fleet currently registers one codex harness type and launches the bundled CLI in long-lived app-server mode (src/ai/fleet/harnessTypes.mjs:20-25, ai/services/fleet/deriveHarnessLaunchSpec.mjs:54-59). Emmy instead required an interactive second Codex Desktop process with a composed contract: an isolated Codex state/auth home, a native Chromium profile, the resident identity, and the resident checkout as the initial project.

The initial two-instance probe against installed ChatGPT/Codex build 26.707.31428 established the launch model:

  • CODEX_ELECTRON_USER_DATA_PATH alone is not the native profile selector. A direct launch with only that env fell back to the existing browser session; adding --user-data-dir=<electron-profile> produced a distinct resident process and profile tree.
  • CODEX_HOME=<codex-home> independently isolated Codex state/auth/memory; open-file inspection showed Emmy's process touching only Emmy's app-profile and Codex-home roots.
  • --open-project=<repoPath> opened the exact resident checkout through argv. A codex:// URL is forbidden for resident routing: macOS LaunchServices delivered it to the already-running default app and the attempted second process exited.
  • CODEX_ELECTRON_USER_DATA_PATH=<electron-profile> remains the app's JS-side mirror of the native profile path, and CODEX_SPARKLE_ENABLED=false made the secondary process log enableSparkle=false enableUpdater=false; one resident remains the updater owner.
  • A sterile launch omitted inherited CODEX_THREAD_ID; the shipped Fleet minimal-env allowlist already provides the correct base for that negative contract.

A fresh direct-spawn probe against build 26.707.41301 validated the positive half on three disposable profiles: the packaged main remained the tracked child, both isolated homes materialized, the exact project persisted, the resident group opened nothing below the default Codex profile, and the main child accepted SIGTERM. It also falsified the former shutdown assumption: two profile-scoped browser_crashpad_handler processes survived each main-process exit, re-parented to PID 1, and remained alive; both --disable-breakpad and --disable-crashpad-for-testing were ineffective. The current build also did not reproduce the older updater-disable log marker.

These installed-build facts are evidence for a capability-gated recipe—not timeless public APIs or an exact-version allowlist. Chromium documents --user-data-dir as the profile override (source); OpenAI documents CODEX_HOME as the Codex state root (source).

The Problem

The Fleet Manager can supervise headless Codex, Claude Code, Claude Desktop, and Antigravity, but cannot launch an isolated interactive Codex Desktop resident. Reusing codex for both modes would collapse two different process/auth/profile contracts behind one durable key:

  • headless codex app-server needs one CODEX_HOME;
  • Codex Desktop needs CODEX_HOME plus native --user-data-dir profile isolation; the app-side profile env mirrors that same path, and --open-project binds the resident checkout without a global URL route;
  • a detached open -n launcher is not a truthful PID/stop handle, matching the existing exclusion at deriveHarnessLaunchSpec.mjs:154-158;
  • main-PID exit is not lifecycle completion on the current build: repeated Fleet start/stop would otherwise leak two Crashpad handlers per isolated profile.

Without a first-class recipe and a profile-bounded helper teardown, the operator must keep hand-launching and hand-cleaning GPT peers outside Fleet ownership.

The Architectural Reality

  • HARNESS_TYPES is the shared Body↔Brain vocabulary; the existing claude-code / claude-desktop split is the direct precedent.
  • deriveAgentInstanceHome owns the contained per-agent home root; the Codex Desktop recipe can derive two child directories beneath that one root rather than inventing another global root.
  • deriveHarnessLaunchSpec is the pure curated-intent map. Registry/UI payloads carry harnessType, never command/args/env.
  • FleetLifecycleService owns the minimal child environment, reserved identity/token injection, PID/state, and stop semantics (lines 109-125).
  • The isolated Electron profile is also the safe ownership boundary for Codex Desktop helper teardown: only packaged browser_crashpad_handler processes whose --database resolves below that exact profile belong to the instance. Generic descendant adoption remains unnecessary and out of scope.
  • Any new executable default belongs in the declarative AiConfig.fleet.harnessBinaries leaf (ai/config.template.mjs:66-93); no use-site env re-derivation.

The Fix

  1. Add a distinct codex-desktop harness type while keeping codex byte-compatible as the headless app-server type.
  2. Add an ADR-0019-conformant Codex Desktop main-binary leaf and a pure launch contract that derives <instanceHome>/codex-home and <instanceHome>/electron-profile.
  3. Spawn the packaged main binary directly under Fleet supervision with --user-data-dir=<electron-profile>. Compose --open-project=<cwd> from the final startAgentProvisioned repo path; never route through codex://.
  4. Inject CODEX_HOME=<codex-home>, CODEX_ELECTRON_USER_DATA_PATH=<electron-profile>, and CODEX_SPARKLE_ENABLED=false only inside the supervised child environment. NEO_AGENT_IDENTITY, the registry PAT, Bridge token, and projection mode remain lifecycle-owned reserved injections; ambient CODEX_THREAD_ID remains excluded.
  5. Probe the packaged main binary before implementation: two distinct homes must coexist, materialize separate state/profile trees, open the requested resident checkout, leave the operator's default profile untouched, and keep the main binary as the directly tracked child.
  6. After the tracked main PID exits, discover only packaged browser_crashpad_handler processes whose --database path resolves below this instance's exact Electron profile, terminate that bounded set, and prove zero residual profile-owned processes. Ambiguous executable/profile ownership fails status and must not broaden into generic descendant cleanup.
  7. Guard the internal app-side profile/open-project/updater contracts with installed-build capability probes. Require a falsifiable updater-disabled marker; if any required capability is absent or ambiguous, surface the family as unavailable before spawn. Never use an exact-version allowlist or claim a detached launcher is running.
  8. Keep OAuth operator-owned. Split GUI launch state from CLI auth state: marker checks use typed authHome=<instanceHome>/codex-home, while authCommand resolves the bundled CLI from AiConfig.fleet.harnessBinaries.codex.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
HARNESS_TYPES: codex-desktop shared harness registry distinct interactive Codex vessel; codex remains headless unknown type remains refused registry JSDoc shared-registry unit pin
Codex Desktop launch tuple deriveHarnessLaunchSpec + final provisioned cwd + installed-build capability probe direct main binary; --user-data-dir=<profile> + --open-project=<repoPath>; CODEX_HOME + app-side profile mirror; secondary updater demonstrably off fail unavailable before spawn; no codex:// / detached running claim / env-only updater assumption launch-contract JSDoc dual-instance + exact-cwd identity live probe + unit matrix
Profile-owned Crashpad teardown tracked main PID + exact Electron profile + live 26.707.41301 falsifier after main exit, terminate only packaged handlers whose --database is below the instance profile; require zero residual profile-owned processes ambiguous executable/profile ownership fails status; never widen to generic descendant adoption lifecycle JSDoc three disposable-profile probes + unit falsifiers for foreign/ambiguous handlers
fleet.harnessBinaries.codexDesktop ADR 0019 / AiConfig executable path read at the use site existing non-executable refusal config JSDoc config lint + lifecycle unit
OAuth handoff/status isolated CODEX_HOME marker contract per-home codex login; no credential bytes cross Fleet DTOs authRequired: true until marker exists conductor help/JSDoc status + command-quoting specs

Decision Record impact

aligned-with ADR 0020 (Fleet-first external-harness lifecycle), ADR 0034 §2.3 (curated Add-Peer intent; no renderer command/env), and ADR 0019 (declarative config ownership). No ADR amendment is required.

Acceptance Criteria

  • codex-desktop is registered once in the shared harness authority; existing codex behavior and key remain unchanged.
  • The recipe derives separate Codex-home and Electron-profile directories beneath the contained per-agent instanceHome; the final provisioned repo path becomes --open-project=<repoPath>.
  • The direct-binary probe proves two concurrent isolated instances, native --user-data-dir profile separation, separate Codex state, exact-project opening, no default-profile mutation, and direct parent/child tracking of the packaged main.
  • After the tracked main exits, the lifecycle discovers and terminates only packaged Crashpad handlers whose --database resolves below that instance's Electron profile; ambiguous ownership fails status, foreign handlers remain untouched, and zero profile-owned processes remain.
  • An unsupported app capability fails before spawn when native profile isolation, the app-side mirror/open-project contract, or a falsifiable updater-disabled marker cannot be proven; status never reports a detached launcher as running.
  • The lifecycle injects the Codex home plus app-profile mirror, disables the secondary updater, and preserves its existing reserved identity/token/PAT variables without exposing raw command/env over the bridge or inheriting CODEX_THREAD_ID.
  • An exact-cwd clean-shell L3 receipt resolves NEO_AGENT_IDENTITY and gh api user --jq .login to the Fleet definition after shell startup; operator dotfiles may not silently shadow the resident binding.
  • Per-home OAuth handoff uses the actual resolved bundled CLI and marker status; credentials remain operator-owned and secret-free in DTO/log output.
  • Existing Codex app-server, Claude Code, Claude Desktop, and Antigravity launch/status specs remain green.
  • Focused unit evidence plus an explicitly labeled L3 local dual-instance receipt are attached to the PR.

Out of Scope

Managed-checkout config/data bootstrap (separate Fleet leaf) · account creation/PAT issuance · OAuth automation · cockpit form design (#14614) · generic descendant adoption or cleanup of helpers not proven to belong to the exact instance profile.

Avoided Traps

  • Do not overload codex with two incompatible modes.
  • Do not treat open -n as a supervised child.
  • Do not freeze an undocumented Electron variable without a revalidation trigger.
  • Do not add raw command/args/env fields to Fleet wire DTOs.
  • Do not weaken shutdown to “the main PID exited”; current-build probes prove that leaks two profile-owned Crashpad handlers per cycle.
  • Do not kill by process name alone; the exact packaged executable plus profile-scoped --database path is the ownership proof.

Related

Parent #13015 · launch-template predecessors #14914, #14972 · GUI-auth ownership #14988 · conductor #14937 · cockpit consumer #14614.

Origin Session ID: de713f27-0e82-4960-b4c6-f281e0c36449

Retrieval Hint: query_raw_memories("Emmy isolated Codex Desktop CODEX_HOME CODEX_ELECTRON_USER_DATA_PATH manual onboarding friction")

tobiu referenced in commit 71a510b - "feat(fleet): add isolated Codex Desktop lifecycle (#15047) (#15069)" on 7:00 AM
tobiu closed this issue on 7:00 AM