LearnNewsExamplesServices
Frontmatter
id15054
titleDisambiguate parallel Codex Desktop wake delivery
stateClosed
labels
bugaitestingregressionarchitecturemodel-experience
assigneesneo-gpt
createdAt3:24 AM
updatedAt5:36 AM
githubUrlhttps://github.com/neomjs/neo/issues/15054
authorneo-gpt
commentsCount2
parentIssue13012
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 15047 Add isolated Codex Desktop launch coverage to Fleet
closedAt5:36 AM

Disambiguate parallel Codex Desktop wake delivery

Closed Backlog/active-chunk-5 bugaitestingregressionarchitecturemodel-experience
neo-gpt
neo-gpt commented on 3:24 AM

Context

The operator re-enabled A2A prompt delivery with two concurrent Codex Desktop residents for the first time. A message durably addressed to @neo-gpt landed inside @neo-gpt-emmy's resident instead. This is a coordination-path correctness failure: the mailbox preserved ownership, while the GUI delivery boundary violated it.

Live V-B-A on 2026-07-12 established:

  • the default resident is an arg-less /Applications/ChatGPT.app/Contents/MacOS/ChatGPT process;
  • Emmy's resident is the same bundle with a distinct --user-data-dir;
  • the app plist exposes CFBundleName=ChatGPT, executable ChatGPT, and bundle id com.openai.codex;
  • the pure resolver returned Codex default -> null, ChatGPT default -> the arg-less main process, and Emmy userDataDir -> the secondary main process;
  • both active Euclid subscriptions then used generic Codex activation, submitted into the wrong resident, and failed Euclid turn-start proof;
  • one later high-priority message matched both active Euclid routes and was submitted twice.

The canonical wake safety gate remains disabled. Prompt delivery must stay incident-scoped until a two-resident positive/negative matrix is green.

Live latest-open sweep: checked the latest 20 open issues at 2026-07-12T01:23Z; no equivalent Codex wake-routing ticket exists. The nearest active items are #15047 (Fleet launch ownership) and #12402 (Claude-only deferred L3 proof). A2A recency sweep found no competing wake-routing claim. Knowledge Base duplicate search found only the closed Claude/general precedents #11822, #12416 / #12422, #13481, and #13744.

The Problem

The wake substrate currently collapses three different application identities into one appName string:

  1. the AppleScript activation name (Codex);
  2. the physical app/process bundle name (ChatGPT.app);
  3. the executable/process name (ChatGPT).

resolveDefaultInstancePid() builds a process marker from appName, so appName:Codex searches for /Codex.app/Contents/MacOS/ and cannot see either live ChatGPT.app resident. The daemon interprets that null as permission to keep the legacy generic tell application "Codex" to activate path. Both residents share bundle id com.openai.codex, so the frontmost guard proves only that some Codex bundle is active—not that the addressed identity's process is active.

That fallback is unsafe whenever multiple same-bundle residents exist. A resolver miss must not degrade an identity-addressed action into bundle-wide activation.

Two live-data gaps amplify the defect:

  • Euclid retains an old all-priority default route plus the current high-priority template route, so high-priority messages can produce two physical prompts.
  • Emmy has no active WAKE_SUBSCRIPTION / HARNESS_PRESENCE; her stable Electron profile is present in the launch argv but is not propagated through the boot envelope into runtime wake registration.

Claude works because its activation, bundle, and executable names align, and the current resolver fixture covers only that shape.

The Architectural Reality

  • ai/daemons/wake/instanceResolver.mjs:282 derives the physical process marker directly from appName.
  • ai/daemons/wake/daemon.mjs:1287-1326 falls back from a null default PID to generic application activation.
  • ai/mcp/server/shared/services/BootEnvelopeResolver.mjs:109-137 already owns machine-local NEO_HARNESS_INSTANCE_ADDRESS + NEO_HARNESS_INSTANCE_ADDRESS_TYPE normalization.
  • ai/services/memory-core/WakeSubscriptionService.mjs already persists the resolved address tuple into the subscription and volatile HarnessPresence.
  • ai/graph/identityRoots.mjs:368-380 gives Euclid a portable static template; ai/graph/identityRoots.mjs:405-417 deliberately leaves Emmy's machine-specific route to first-boot self-registration.
  • test/playwright/unit/ai/daemons/wake/instanceResolver.spec.mjs:84-128 proves the default/sibling split only for Claude.app.
  • ai/config.mjs:93 uses the current ChatGPT-packaged Codex CLI, while ai/daemons/wake/daemon.mjs:87 and ai/scripts/lifecycle/resumeHarness.mjs:130 retain stale /Applications/Codex.app fallbacks.

The owning substrate is the existing wake resolver/daemon + boot-envelope registration path. No new service or new .mjs directory is needed.

The Fix

  1. Separate activation identity from physical process identity for GUI harnesses. Codex must retain its validated activation semantics while default-instance resolution matches ChatGPT.app / ChatGPT (or derives the physical tuple from one canonical app-identity mapping).
  2. Make default-instance resolution return enough state to distinguish single-instance legacy behavior from multi-instance unresolved/ambiguous behavior. Multiple same-bundle residents plus no exact PID must fail closed; never fall through to generic activation.
  3. Preserve explicit userDataDir targeting as the stable secondary-resident address. Ensure the real Codex Desktop launch/bootstrap path supplies Emmy's Electron profile through the existing boot-envelope contract without committing a machine path into identityRoots.mjs.
  4. Repair live routes: retain one intentional Euclid route matching current policy, retire the overlapping legacy route, and materialize Emmy's instance-addressed route/presence from her real boot.
  5. Remove stale packaged-Codex path fallbacks in wake/resume code in favor of the existing AiConfig authority.
  6. Add Codex-specific pure/unit fixtures plus an operator-visible 2x-Codex prompt-landing matrix with negative cross-delivery evidence.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
GUI app identity tuple used by instanceResolver live ChatGPT.app plist + existing appName:Codex wake contract activation name and physical process identity are distinct inputs; Codex resolves the arg-less ChatGPT main unknown/missing physical identity fails closed under multi-instance detection JSDoc in existing resolver/daemon pure resolver fixtures for Codex + Claude
default Shape-C wake dispatch daemon.mjs#deliverDigest + closed #12399 contract exactly resolved PID is raised before any prompt mutation when sibling residents exist unresolved/ambiguous multi-instance target produces no keystrokes and an actionable error JSDoc/log wording daemon subprocess spec + negative fixture
boot-envelope address tuple BootEnvelopeResolver + WakeSubscriptionService.bootstrap Emmy boot supplies addressType:userDataDir and her stable Electron profile; presence echoes the tuple partial/missing tuple rejects registration; no generic sibling route existing JSDoc service specs + live subscription audit
active per-identity subscription set current AgentIdentity template + live graph audit one physical wake per qualifying message/identity obsolete overlapping route is retired, not silently co-delivered migration/incident evidence before/after audit + one-message proof
packaged Codex executable path AiConfig.fleet.harnessBinaries.codex wake/resume consumers use current config authority missing/non-executable path fails with existing guard JSDoc config lint + focused specs

Decision Record impact

None. This restores the already-shipped same-bundle instance-address contract from #12399 / #12422 and stays within the existing local-GUI boundary.

Acceptance Criteria

  • Codex-specific resolver fixtures prove that appName:Codex can activate the product while the physical ChatGPT.app tuple resolves the arg-less default PID and the explicit Emmy userDataDir resolves only the secondary PID.
  • When two same-bundle Codex residents exist, a null/ambiguous target refuses delivery before activation, clipboard mutation, or keystrokes; generic fallback remains available only for a proven single-instance legacy case.
  • Euclid has one intentional active route and a qualifying high-priority message produces one prompt, not two.
  • Emmy self-registers one active route/presence carrying her stable userDataDir; no machine-specific profile path lands in committed identity roots.
  • Live matrix passes over at least two controlled cycles each: Euclid message -> Euclid only; Emmy message -> Emmy only; unavailable addressed resident -> no sibling delivery.
  • Stale /Applications/Codex.app fallback literals in wake/resume are removed in favor of the current config authority.
  • Focused wake resolver, daemon, subscription/bootstrap, and migration tests pass without changing Claude behavior.
  • The wake safety gate is re-enabled only after backlog fencing and the live matrix evidence are recorded.

Out of Scope

  • Implementing Fleet's Codex Desktop spawn/auth lifecycle (#15047).
  • Changing A2A mailbox storage or recipient semantics.
  • Reworking Claude routes that already pass the instance-address matrix.
  • Adding a new GUI-harness service or committing machine-local paths.

Avoided Traps

  • Do not globally rename the activation appName to ChatGPT; activation identity and process identity are separate contracts.
  • Do not interpret resolver null as safe generic activation when sibling residents exist.
  • Do not hard-code an operator's userDataDir in identityRoots.mjs.
  • Do not use an exact app-version allowlist where a capability fingerprint is the real compatibility contract.
  • Do not split this one incident loop into micro-tickets; code, live route repair, and the negative matrix are one correctness outcome.

Related

Parent: #13012 Blocks: #15047 Related: #15052 Related: #12402 Related: #12416 Related: #12422 Related: #13481 Related: #13744

Origin Session ID: 837ad74b-c2d2-413d-9aab-b7165a93a82a

Retrieval Hint: parallel Codex Desktop ChatGPT.app wrong resident wake @neo-gpt @neo-gpt-emmy default PID null duplicate subscriptions boot envelope Retrieval Hint: Memory Core 8d32f508-81ed-4c90-a437-ceaad8fe330e

tobiu referenced in commit 6517694 - "fix(wake): disambiguate parallel Codex residents (#15054) (#15064) on 5:36 AM
tobiu closed this issue on 5:36 AM