Context
Surfaced empirically during post-#10441 wake-substrate routing diagnosis (2026-04-27, session c68a7d4b). When WAKE_SUB:b3d1179c-... was created with empty harnessTargetMetadata: {} (per the parallel #10442 ticket on update action's overwrite behavior), wake delivery should have failed visibly. Instead it succeeded — but routed to the wrong harness.
The empirical pattern:
- Wake event for
@neo-gemini-pro fires through bridge daemon
- Bridge daemon reads sub's
harnessTargetMetadata → {} (empty)
bridge-daemon.mjs:476: const appName = meta.appName || 'Claude' → defaults to 'Claude'
- Bridge daemon dispatches
osascript -e 'tell application "Claude" to activate' → activates @tobiu's Claude desktop
- Wake payload
[WAKE] 1 events for @neo-gemini-pro: ... lands on the wrong harness
- Bridge daemon log shows
Delivered ... via osascript to Claude — looks like success
The wake didn't reach Antigravity (Gemini's intended harness). The misroute went undetected until inspection of bridge.log's to <appName> field revealed it.
The Problem
The current fallback semantics are dangerously permissive:
const appName = meta.appName || 'Claude';
When meta.appName is missing/empty/null, the daemon silently defaults to 'Claude'. Three problems:
Severity tier-up over the prior 'Cursor' bug: before #10440, Gemini's sub had appName: 'Cursor' which failed visibly (osascript exit 1 — easy to diagnose). Now the empty-metadata case routes to 'Claude' silently — looks like success but isn't reaching the intended recipient.
Misroute to the host's Claude desktop is operationally noisy. @tobiu is the human commander running both harnesses on one machine; misrouted wakes inject text into HIS active Claude session, polluting cross-agent context.
The default mass effect: 'Claude' as fallback is host-specific (Claude Desktop must be installed). For agents with neither Claude nor a configured appName, the failure mode is osascript exit 1 (visible). For Tobi's host (Claude installed), the misroute is silent. Same broken sub, different observable behavior depending on host config — exactly the property that masks bugs.
The Architectural Reality
ai/scripts/bridge-daemon.mjs:466-510 (osascript adapter, exact line per current dev)
ai/scripts/bridge-daemon.mjs:476: the offending fallback meta.appName || 'Claude'
ai/graph/identityRoots.mjs — canonical source for subscriptionTemplate.harnessTargetMetadata.appName
ai/mcp/server/memory-core/services/WakeSubscriptionService.mjs:bootstrap() — copies metadata from template to sub at subscribe-time
The Phase 3 wake substrate explicitly designed harnessTargetMetadata.appName as a required, identity-template-derived field per #10402's footgun-closure scope. The fallback || 'Claude' is a footgun that survives the Phase 3 fix because the template-driven path always provides a value — until something (like #10442's update bug) leaves the field empty.
The Fix
Replace the silent fallback with a fail-fast + observable path:
const appName = meta.appName;
if (!appName) {
writeLog('ERROR',
`[Bridge Daemon] Cannot deliver subscription ${subscription.id}: ` +
`harnessTargetMetadata.appName is missing/empty. ` +
`Skipping delivery to avoid misrouting. ` +
`Verify subscription template via 'manage_wake_subscription({action: 'list'})' or fix the AgentIdentity subscriptionTemplate.`
);
return;
}Properties of the fix:
- Fail-fast: missing appName → no delivery attempt, no silent misroute
- Observable:
bridge.log (per #10425) captures the skip reason with the offending sub-id + corrective hint
- Diagnostic-friendly: the agent investigating later sees exactly which sub is misconfigured
- No regression on configured subs: valid
meta.appName (the dominant path post-#10402) still delivers normally
Acceptance Criteria
Out of Scope
- Migration of existing empty-metadata subs (the parallel #10442 fix on
update action is the canonical-source fix; this ticket addresses the daemon-side defense)
- A retry-with-different-app-name strategy (introduces magic; the agent should fix the sub config explicitly)
- Validating
appName against a known-good list (e.g., ['Claude', 'Antigravity', 'Cursor']) — out of scope; let the OS-level osascript exit 1 handle unknown apps; this ticket focuses on the missing-field case only
- Equivalent fail-fast for missing
harnessTarget (likely already handled higher up; verify but don't expand scope unless gap surfaces)
Avoided Traps
- Trap: Replace fallback with a configurable env var for the default. Avoided: the substrate already provides identity-template-derived metadata as the canonical source; layering a host-specific env var fallback would fragment the contract.
- Trap: Add a "smart" detection (e.g., probe running apps to guess the right one). Avoided: wake substrate routing is a contract, not a heuristic. Misconfiguration should fail visibly; agents have
manage_wake_subscription to fix it.
- Trap: Keep the fallback but log a warning. Avoided: delivery still misroutes; the log line might not be checked. Skipping the delivery entirely is the substrate-grounded fail-fast that drives diagnostic visibility.
Related
- Empirical anchor: post-#10441 routing diagnosis where
WAKE_SUB:b3d1179c (Gemini's empty-metadata sub) misrouted to host's Claude
- Parallel root cause: #10442 (
update action's overwrite-vs-merge semantics that produced the empty metadata)
- Persistent log substrate that captured the misroute: #10419 / PR #10425
- Original footgun-closure scope: #10402 (Phase 1 wake substrate self-healing — addressed the
appName template path; this addresses the missing-template fallback)
Origin Session ID: c68a7d4b-909a-4965-9bf9-116906d271a3
Retrieval Hint: "bridge daemon silent fallback Claude default empty harnessTargetMetadata appName misroute fail-fast skip-delivery"
Context
Surfaced empirically during post-#10441 wake-substrate routing diagnosis (2026-04-27, session
c68a7d4b). WhenWAKE_SUB:b3d1179c-...was created with emptyharnessTargetMetadata: {}(per the parallel #10442 ticket onupdateaction's overwrite behavior), wake delivery should have failed visibly. Instead it succeeded — but routed to the wrong harness.The empirical pattern:
@neo-gemini-profires through bridge daemonharnessTargetMetadata→{}(empty)bridge-daemon.mjs:476:const appName = meta.appName || 'Claude'→ defaults to'Claude'osascript -e 'tell application "Claude" to activate'→ activates@tobiu's Claude desktop[WAKE] 1 events for @neo-gemini-pro: ...lands on the wrong harnessDelivered ... via osascript to Claude— looks like successThe wake didn't reach Antigravity (Gemini's intended harness). The misroute went undetected until inspection of
bridge.log'sto <appName>field revealed it.The Problem
The current fallback semantics are dangerously permissive:
// ai/scripts/bridge-daemon.mjs:476 (paraphrased) const appName = meta.appName || 'Claude';When
meta.appNameis missing/empty/null, the daemon silently defaults to'Claude'. Three problems:Severity tier-up over the prior
'Cursor'bug: before #10440, Gemini's sub hadappName: 'Cursor'which failed visibly (osascript exit 1— easy to diagnose). Now the empty-metadata case routes to'Claude'silently — looks like success but isn't reaching the intended recipient.Misroute to the host's Claude desktop is operationally noisy.
@tobiuis the human commander running both harnesses on one machine; misrouted wakes inject text into HIS active Claude session, polluting cross-agent context.The default mass effect:
'Claude'as fallback is host-specific (Claude Desktop must be installed). For agents with neither Claude nor a configuredappName, the failure mode isosascript exit 1(visible). For Tobi's host (Claude installed), the misroute is silent. Same broken sub, different observable behavior depending on host config — exactly the property that masks bugs.The Architectural Reality
ai/scripts/bridge-daemon.mjs:466-510(osascript adapter, exact line per current dev)ai/scripts/bridge-daemon.mjs:476: the offending fallbackmeta.appName || 'Claude'ai/graph/identityRoots.mjs— canonical source forsubscriptionTemplate.harnessTargetMetadata.appNameai/mcp/server/memory-core/services/WakeSubscriptionService.mjs:bootstrap()— copies metadata from template to sub at subscribe-timeThe Phase 3 wake substrate explicitly designed
harnessTargetMetadata.appNameas a required, identity-template-derived field per #10402's footgun-closure scope. The fallback|| 'Claude'is a footgun that survives the Phase 3 fix because the template-driven path always provides a value — until something (like #10442's update bug) leaves the field empty.The Fix
Replace the silent fallback with a fail-fast + observable path:
const appName = meta.appName; if (!appName) { writeLog('ERROR', `[Bridge Daemon] Cannot deliver subscription ${subscription.id}: ` + `harnessTargetMetadata.appName is missing/empty. ` + `Skipping delivery to avoid misrouting. ` + `Verify subscription template via 'manage_wake_subscription({action: 'list'})' or fix the AgentIdentity subscriptionTemplate.` ); return; // skip delivery; log surfaces the misconfiguration }Properties of the fix:
bridge.log(per #10425) captures the skip reason with the offending sub-id + corrective hintmeta.appName(the dominant path post-#10402) still delivers normallyAcceptance Criteria
bridge-daemon.mjsremoves the|| 'Claude'fallbackmeta.appNametriggers anERROR-level log line +return(no delivery attempt)bridge-daemon.spec.mjstests still pass (no regression on the configured-appName path)osascriptinvocationharnessTargetMetadata; trigger a wake event; verify bridge.log shows the ERROR skip line + no misroute to host's ClaudeOut of Scope
updateaction is the canonical-source fix; this ticket addresses the daemon-side defense)appNameagainst a known-good list (e.g.,['Claude', 'Antigravity', 'Cursor']) — out of scope; let the OS-levelosascript exit 1handle unknown apps; this ticket focuses on the missing-field case onlyharnessTarget(likely already handled higher up; verify but don't expand scope unless gap surfaces)Avoided Traps
manage_wake_subscriptionto fix it.Related
WAKE_SUB:b3d1179c(Gemini's empty-metadata sub) misrouted to host's Claudeupdateaction's overwrite-vs-merge semantics that produced the empty metadata)appNametemplate path; this addresses the missing-template fallback)Origin Session ID: c68a7d4b-909a-4965-9bf9-116906d271a3
Retrieval Hint: "bridge daemon silent fallback Claude default empty harnessTargetMetadata appName misroute fail-fast skip-delivery"