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:
- the AppleScript activation name (
Codex);
- the physical app/process bundle name (
ChatGPT.app);
- 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
- 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).
- 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.
- 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.
- 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.
- Remove stale packaged-Codex path fallbacks in wake/resume code in favor of the existing AiConfig authority.
- 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
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
Context
The operator re-enabled A2A prompt delivery with two concurrent Codex Desktop residents for the first time. A message durably addressed to
@neo-gptlanded 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:
/Applications/ChatGPT.app/Contents/MacOS/ChatGPTprocess;--user-data-dir;CFBundleName=ChatGPT, executableChatGPT, and bundle idcom.openai.codex;Codex default -> null,ChatGPT default -> the arg-less main process, andEmmy userDataDir -> the secondary main process;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
appNamestring:Codex);ChatGPT.app);ChatGPT).resolveDefaultInstancePid()builds a process marker fromappName, soappName:Codexsearches for/Codex.app/Contents/MacOS/and cannot see either liveChatGPT.appresident. The daemon interprets that null as permission to keep the legacy generictell application "Codex" to activatepath. Both residents share bundle idcom.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:
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:282derives the physical process marker directly fromappName.ai/daemons/wake/daemon.mjs:1287-1326falls back from a null default PID to generic application activation.ai/mcp/server/shared/services/BootEnvelopeResolver.mjs:109-137already owns machine-localNEO_HARNESS_INSTANCE_ADDRESS+NEO_HARNESS_INSTANCE_ADDRESS_TYPEnormalization.ai/services/memory-core/WakeSubscriptionService.mjsalready persists the resolved address tuple into the subscription and volatileHarnessPresence.ai/graph/identityRoots.mjs:368-380gives Euclid a portable static template;ai/graph/identityRoots.mjs:405-417deliberately leaves Emmy's machine-specific route to first-boot self-registration.test/playwright/unit/ai/daemons/wake/instanceResolver.spec.mjs:84-128proves the default/sibling split only forClaude.app.ai/config.mjs:93uses the current ChatGPT-packaged Codex CLI, whileai/daemons/wake/daemon.mjs:87andai/scripts/lifecycle/resumeHarness.mjs:130retain stale/Applications/Codex.appfallbacks.The owning substrate is the existing wake resolver/daemon + boot-envelope registration path. No new service or new
.mjsdirectory is needed.The Fix
ChatGPT.app/ChatGPT(or derives the physical tuple from one canonical app-identity mapping).userDataDirtargeting 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 intoidentityRoots.mjs.Contract Ledger
instanceResolverappName:Codexwake contractdaemon.mjs#deliverDigest+ closed#12399contractBootEnvelopeResolver+WakeSubscriptionService.bootstrapaddressType:userDataDirand her stable Electron profile; presence echoes the tupleAiConfig.fleet.harnessBinaries.codexDecision Record impact
None. This restores the already-shipped same-bundle instance-address contract from
#12399/#12422and stays within the existing local-GUI boundary.Acceptance Criteria
appName:Codexcan activate the product while the physicalChatGPT.apptuple resolves the arg-less default PID and the explicit EmmyuserDataDirresolves only the secondary PID.userDataDir; no machine-specific profile path lands in committed identity roots./Applications/Codex.appfallback literals in wake/resume are removed in favor of the current config authority.Out of Scope
#15047).Avoided Traps
appNametoChatGPT; activation identity and process identity are separate contracts.nullas safe generic activation when sibling residents exist.userDataDirinidentityRoots.mjs.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 envelopeRetrieval Hint: Memory Core8d32f508-81ed-4c90-a437-ceaad8fe330e