@minhspark/codex-mcp-bridge 1.16.0 → 1.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,11 +20,13 @@ import { ReplyForwarder } from "./reply-forwarder.mjs";
20
20
  import { assertRoutingReload, clientReloadReason, createReloadControl } from "./reload-control.mjs";
21
21
  import { readClaudeAccountContext } from "./desktop-account-context.mjs";
22
22
  import { assertAccountIdentity, bindUnsolicitedClaudeMessageAccount, publicAccountState, readBridgeAccounts, requireBridgeAccounts, sameAccountIdentity } from "./bridge-account-context.mjs";
23
- import { resolveClaudeDesktopSession } from "./claude-session-router.mjs";
23
+ import { resolveClaudeDesktopSession, sameClaudeDesktopRecipient } from "./claude-session-router.mjs";
24
+ import { createHardenedRootPolicy } from "./hardened-root-policy.mjs";
24
25
 
25
26
  exitForVersionRequest(import.meta.url);
26
27
 
27
- const VERSION = "1.16.0";
28
+ const VERSION = "1.16.1";
29
+ void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
28
30
  const FORWARD_MIN_INTERVAL_MS = 5000;
29
31
  const FORWARD_MAX_PER_SESSION = 50;
30
32
 
@@ -32,6 +34,7 @@ const log = (msg) => process.stderr.write(`[claude-bridge] ${msg}\n`);
32
34
 
33
35
  const defaultPeerName = process.env.CLAUDE_BRIDGE_PEER_NAME ?? `codex-${process.pid}`;
34
36
  const desktopOnly = desktopTasksConfigured();
37
+ const rootPolicy = createHardenedRootPolicy();
35
38
  const runtime = createRuntimeState({ configuration: desktopTasksConfigured });
36
39
 
37
40
  const peer = new PeerEndpoint({
@@ -57,15 +60,34 @@ const forwarding = {
57
60
  threadId: process.env.CODEX_THREAD_ID ?? null,
58
61
  };
59
62
 
63
+ const creationRootBindings = new Map();
64
+
60
65
  const creations = new ClaudeSessionCreation({
61
66
  open: openClaudeCodeComposer,
62
- listTasks: (account) => readClaudeDesktopTasks({ account }),
63
- listSessions: () => listClaudeSessions(),
67
+ listTasks: (account) => readClaudeDesktopTasks({ account }).filter((task) => rootPolicy.allows(task.cwd)),
68
+ listSessions: () => listClaudeSessions().filter((session) => rootPolicy.allows(session.cwd)),
64
69
  readContext: (session, account) => readClaudeDesktopContext(session, { account }),
65
- readTranscript: readInitialTranscriptMessage,
70
+ readTranscript: (...args) => {
71
+ const binding = rootPolicy.capture(args[1], "Claude creation transcript working directory");
72
+ const transcript = readInitialTranscriptMessage(...args);
73
+ rootPolicy.recheck(binding, "Claude creation transcript working directory");
74
+ return transcript;
75
+ },
66
76
  readAccount: readClaudeAccountContext,
67
77
  assertProcess: assertClaudeSessionProcess,
68
78
  });
79
+ function recheckScopeBindings(bindings) {
80
+ if (!rootPolicy.enabled) return;
81
+ if (!bindings?.sender || !bindings?.recipient) throw Object.assign(new Error("Hardened bridge record has no original sender and recipient root binding"), { code: "ROOT_BINDING_MISSING" });
82
+ rootPolicy.recheck(bindings.sender, "Original Codex sender working directory");
83
+ rootPolicy.recheck(bindings.recipient, "Original Claude recipient working directory");
84
+ }
85
+
86
+ function recordVisible(record, accounts = readBridgeAccounts()) {
87
+ if (desktopOnly && !sameAccountIdentity(record?.accountContext, accounts)) return false;
88
+ if (!rootPolicy.enabled) return true;
89
+ try { recheckScopeBindings(record?.scopeBindings); return true; } catch { return false; }
90
+ }
69
91
 
70
92
  const replyForwarder = new ReplyForwarder({
71
93
  minIntervalMs: FORWARD_MIN_INTERVAL_MS,
@@ -75,16 +97,17 @@ const replyForwarder = new ReplyForwarder({
75
97
  throw Object.assign(new Error("Bridge routing changed; the reply was not forwarded. Inspect the original receipt before reconnecting."), { code: "REPLY_ROUTING_CHANGED" });
76
98
  }
77
99
  if (desktopOnly) assertAccountIdentity(record.accountContext);
100
+ recheckScopeBindings(record.scopeBindings);
78
101
  },
79
102
  deliver: (threadId, record) => delivery.deliver(threadId, `[message from Claude session ${record.fromSocket ?? "?"}]\n${record.absorbed ? "[This reply is the closing text of a turn that absorbed the message while it was running; it may not address the message.]\n" : ""}\n${record.text}`,
80
- desktopOnly ? { beforeSend: () => assertAccountIdentity(record.accountContext), accountContext: record.accountContext } : {}),
103
+ desktopOnly ? { beforeSend: () => { assertAccountIdentity(record.accountContext); recheckScopeBindings(record.scopeBindings); }, accountContext: record.accountContext } : {}),
81
104
  });
82
105
 
83
106
  const reload = createReloadControl({
84
107
  entry: "claude-bridge.mjs",
85
108
  inspect: () => peer.reloadReason() ?? replyForwarder.reloadReason() ?? clientReloadReason(codex),
86
109
  quiesce: async () => { await peer.quiesce(); codex.close(); },
87
- exportState: () => ({ desktopOnly, peer: peer.exportReloadState(), forwarding: replyForwarder.exportReloadState(), threadId: forwarding.threadId, creations: creations.exportState() }),
110
+ exportState: () => ({ desktopOnly, peer: peer.exportReloadState(), forwarding: replyForwarder.exportReloadState(), threadId: forwarding.threadId, creations: creations.exportState(), creationRootBindings: [...creationRootBindings] }),
88
111
  restore: (state) => {
89
112
  assertRoutingReload(state.desktopOnly, desktopOnly);
90
113
  if (state.threadId !== null && (typeof state.threadId !== "string" || !state.threadId.trim())) {
@@ -94,6 +117,13 @@ const reload = createReloadControl({
94
117
  replyForwarder.restoreReloadState(state.forwarding);
95
118
  forwarding.threadId = state.threadId;
96
119
  if (state.creations !== undefined) creations.restoreState(state.creations);
120
+ if (state.creationRootBindings !== undefined) {
121
+ if (!Array.isArray(state.creationRootBindings) || state.creationRootBindings.length > 32) throw new Error("Invalid Claude creation root bindings");
122
+ for (const entry of state.creationRootBindings) {
123
+ if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string" || creationRootBindings.has(entry[0]) || !creations.requests.has(entry[0])) throw new Error("Invalid Claude creation root binding");
124
+ creationRootBindings.set(entry[0], entry[1]);
125
+ }
126
+ }
97
127
  },
98
128
  activate: () => peer.start(),
99
129
  resume: () => peer.resume(),
@@ -102,6 +132,9 @@ const reload = createReloadControl({
102
132
  function readReceipt(msgId) {
103
133
  const receipt = peer.readDelivery(msgId);
104
134
  if (desktopOnly && receipt && !sameAccountIdentity(receipt.accountContext, readBridgeAccounts())) return null;
135
+ if (rootPolicy.enabled) {
136
+ try { recheckScopeBindings(receipt?.scopeBindings); } catch { return null; }
137
+ }
105
138
  return receipt ? { ...receipt, forwarding: replyForwarder.read(msgId) ?? receipt.forwardingError ?? null } : null;
106
139
  }
107
140
 
@@ -130,6 +163,10 @@ function withDesktopContext(session, account = readClaudeAccountContext()) {
130
163
  ? { ...session, desktop: readClaudeDesktopContext(session, { account }), inbound: readClaudeInboundPolicy(session.cwd) } : session;
131
164
  }
132
165
 
166
+ function assertSessionRoot(session, label = "Claude session") {
167
+ return rootPolicy.assert(session?.cwd, `${label} working directory`);
168
+ }
169
+
133
170
  function assertDesktopTask(session, expectedTaskId) {
134
171
  if (session.desktop?.status !== "matched") {
135
172
  throw preflightFailure("CLAUDE_DESKTOP_TASK_UNVERIFIED", session.desktop?.reason ?? "The live session could not be matched to a Claude Desktop task.");
@@ -147,28 +184,44 @@ function assertSender(meta) {
147
184
  return sender;
148
185
  }
149
186
 
187
+ function assertCreationRoots(requestId, sender) {
188
+ if (!rootPolicy.enabled) return;
189
+ rootPolicy.assert(sender.cwd, "Codex sender working directory");
190
+ recheckScopeBindings(creationRootBindings.get(requestId));
191
+ }
192
+
150
193
  async function startClaudeCreation({ requestId, cwd, prompt }, meta) {
151
194
  runtime.assertCurrent();
152
195
  if (!desktopOnly) throw new Error("New Claude sessions require Desktop-only mode");
153
196
  const sender = assertSender(meta);
154
197
  const accounts = readBridgeAccounts();
155
198
  const selectedAccounts = requireBridgeAccounts(accounts);
199
+ if (rootPolicy.enabled && creations.requests.has(requestId)) assertCreationRoots(requestId, sender);
200
+ const scopeBindings = rootPolicy.enabled ? creationRootBindings.get(requestId) ?? {
201
+ sender: rootPolicy.capture(sender.cwd, "Codex sender working directory"),
202
+ recipient: rootPolicy.capture(cwd, "Claude creation working directory"),
203
+ } : null;
156
204
  const receipt = await creations.start({ requestId, cwd, prompt, account: accounts.claude, senderThreadId: sender.threadId,
157
205
  beforeOpen: () => {
158
206
  runtime.assertCurrent();
159
207
  assertAccountIdentity(selectedAccounts);
208
+ recheckScopeBindings(scopeBindings);
209
+ if (rootPolicy.enabled && !rootPolicy.same(cwd, scopeBindings.recipient.path)) throw new Error("The Claude creation working directory changed before opening Desktop");
160
210
  const current = assertSender(meta);
161
211
  if (JSON.stringify(current) !== JSON.stringify(sender)) throw new Error("The calling Codex turn changed before opening Claude Desktop");
162
212
  },
163
213
  });
214
+ if (rootPolicy.enabled) creationRootBindings.set(requestId, scopeBindings);
164
215
  return { ...textResult(JSON.stringify(receipt, null, 2)), structuredContent: { creation: receipt } };
165
216
  }
166
217
 
167
218
  async function inspectClaudeCreation(requestId, meta) {
168
219
  const sender = assertSender(meta);
220
+ assertCreationRoots(requestId, sender);
169
221
  const accounts = readBridgeAccounts();
170
222
  requireBridgeAccounts(accounts);
171
223
  const receipt = await creations.inspect(requestId, { account: accounts.claude, senderThreadId: sender.threadId });
224
+ assertCreationRoots(requestId, sender);
172
225
  return { ...textResult(JSON.stringify(receipt, null, 2)), structuredContent: { creation: receipt } };
173
226
  }
174
227
 
@@ -184,6 +237,9 @@ function forwardToCodexThread(record) {
184
237
  }
185
238
 
186
239
  peer.onMessage((record) => {
240
+ if (rootPolicy.enabled && !record.cwd) { record.forwardingError = { status: "blocked", reasonCode: "ROOT_UNAUTHORIZED", reason: "Unsolicited inbound messages are disabled in hardened mode without a verified root binding" }; return; }
241
+ try { if (rootPolicy.enabled) rootPolicy.assert(record.cwd, "Inbound Claude message working directory"); }
242
+ catch (err) { record.forwardingError = { status: "blocked", reasonCode: "ROOT_UNAUTHORIZED", reason: err.message }; return; }
187
243
  if (desktopOnly) bindUnsolicitedClaudeMessageAccount(record, {
188
244
  hasPendingSource: (socket) => [...peer.pendingMessages.values()].some((pending) => pending.targetSocket === socket),
189
245
  });
@@ -252,6 +308,7 @@ registerTool(
252
308
  },
253
309
  async ({ requestId }, extra) => {
254
310
  const sender = assertSender(extra?._meta);
311
+ assertCreationRoots(requestId, sender);
255
312
  const accounts = readBridgeAccounts();
256
313
  requireBridgeAccounts(accounts);
257
314
  const receipt = await creations.abandon(requestId, { account: accounts.claude, senderThreadId: sender.threadId });
@@ -277,11 +334,13 @@ registerTool(
277
334
  },
278
335
  async ({ includeDead, expectedCwd }) => {
279
336
  try {
337
+ if (rootPolicy.enabled && expectedCwd !== undefined) rootPolicy.assert(expectedCwd, "Requested working directory");
280
338
  if (expectedCwd !== undefined) assertClaudeSessionCwd({ cwd: expectedCwd }, expectedCwd);
281
339
  const account = readClaudeAccountContext();
282
340
  if (desktopOnly && account.status !== "verified") return { ...textResult(`Claude account ${account.status}: ${account.reason} Sign in and retry discovery.`, true), structuredContent: { account: publicAccountState(account), sessions: [] } };
283
341
  const sessions = listClaudeSessions({ includeDead: includeDead ?? false }).filter(
284
342
  (s) => s.pid !== process.pid && (!desktopOnly || s.entrypoint === "claude-desktop"),
343
+ ).filter((session) => !rootPolicy.enabled || rootPolicy.allows(session.cwd)
285
344
  ).filter((session) => {
286
345
  if (expectedCwd === undefined) return true;
287
346
  try { assertClaudeSessionCwd(session, expectedCwd); return true; }
@@ -345,6 +404,7 @@ registerTool(
345
404
  }) : findClaudeSession(target, { desktopOnly });
346
405
  if (!found) return textResult(desktopOnly ? missingDesktopSession : `No live Claude session matches "${target}".`, true);
347
406
  const session = desktopOnly ? { ...found, inbound: readClaudeInboundPolicy(found.cwd) } : withDesktopContext(found);
407
+ assertSessionRoot(session);
348
408
  const selectedTaskId = target === "auto" ? session.desktop?.taskId : expectedTaskId;
349
409
  if (desktopOnly || expectedCwd !== undefined) assertClaudeSessionCwd(session, expectedCwd);
350
410
  if (desktopOnly) {
@@ -352,6 +412,10 @@ registerTool(
352
412
  assertClaudeSessionProcess(session);
353
413
  }
354
414
  const sender = desktopOnly ? assertSender(extra?._meta) : null;
415
+ const scopeBindings = rootPolicy.enabled ? {
416
+ sender: rootPolicy.capture(sender?.cwd, "Codex sender working directory"),
417
+ recipient: rootPolicy.capture(session.cwd, "Claude recipient working directory"),
418
+ } : null;
355
419
  if (desktopOnly) assertRecipientClass(session, sender);
356
420
  await peer.start();
357
421
 
@@ -363,19 +427,28 @@ registerTool(
363
427
  ...(selectedAccounts ? { accountContext: selectedAccounts } : {}),
364
428
  ...(sender ? { permissionMode: sender.mode, replyThreadId: sender.threadId, senderReview: sender.review, senderApprovalPolicy: sender.approvalPolicy } : {}),
365
429
  ...(desktop ? { recipient: { permissionMode: session.desktop.permissionMode ?? null, permissionClass: session.desktop.permissionClass ?? null, inboundPolicy: session.inbound?.value ?? null } } : {}),
430
+ ...(scopeBindings ? { scopeBindings } : {}),
366
431
  beforeSend: () => {
367
432
  runtime.assertCurrent();
368
433
  if (desktopOnly) assertAccountIdentity(selectedAccounts);
369
434
  const current = findClaudeSession(session.sessionId ?? String(session.pid), { desktopOnly });
370
- if (!current || current.pid !== session.pid || current.socket !== session.socket) {
435
+ const destinationUnchanged = desktopOnly
436
+ ? sameClaudeDesktopRecipient(session, current)
437
+ : Boolean(current && current.pid === session.pid && current.socket === session.socket);
438
+ if (!destinationUnchanged) {
371
439
  throw new Error("The Claude destination changed while this message was queued. No message was sent; inspect the existing Desktop session.");
372
440
  }
373
441
  if (desktopOnly || expectedCwd !== undefined) assertClaudeSessionCwd(current, expectedCwd);
442
+ assertSessionRoot(current);
374
443
  if (desktopOnly) {
375
444
  assertClaudeSessionProcess(current);
376
445
  const refreshed = withDesktopContext(current, readClaudeAccountContext());
377
446
  assertDesktopTask(refreshed, selectedTaskId);
378
447
  const active = assertSender(extra?._meta);
448
+ if (rootPolicy.enabled) {
449
+ rootPolicy.recheck(scopeBindings.sender, "Codex sender working directory");
450
+ rootPolicy.recheck(scopeBindings.recipient, "Claude recipient working directory");
451
+ }
379
452
  if (active.threadId !== sender.threadId || active.turnId !== sender.turnId || active.cwd !== sender.cwd || active.mode !== sender.mode || active.approvalPolicy !== sender.approvalPolicy || JSON.stringify(active.review) !== JSON.stringify(sender.review)) {
380
453
  throw preflightFailure("CODEX_SENDER_CONTEXT_CHANGED", "The sender's active turn or permissions changed while this message was queued.");
381
454
  }
@@ -460,7 +533,7 @@ registerTool(
460
533
  try {
461
534
  await peer.start();
462
535
  const accounts = desktopOnly ? readBridgeAccounts() : null;
463
- const messages = peer.drainInbox(limit ?? 20, (record) => !desktopOnly || sameAccountIdentity(record.accountContext, accounts));
536
+ const messages = peer.drainInbox(limit ?? 20, (record) => recordVisible(record, accounts));
464
537
  if (!messages.length) return textResult(desktopOnly ? "No messages for the current accounts." : "Inbox is empty.");
465
538
  const result = textResult(
466
539
  messages
@@ -471,7 +544,8 @@ registerTool(
471
544
  })
472
545
  .join("\n\n"),
473
546
  );
474
- return { ...result, structuredContent: { messages: messages.map((record) => ({ ...record, forwarding: replyForwarder.read(record.inReplyTo ?? record.msgId) ?? record.forwardingError ?? null })), remaining: peer.inbox.length } };
547
+ const remaining = peer.inbox.filter((record) => recordVisible(record, accounts)).length;
548
+ return { ...result, structuredContent: { messages: messages.map((record) => ({ ...record, forwarding: replyForwarder.read(record.inReplyTo ?? record.msgId) ?? record.forwardingError ?? null })), remaining } };
475
549
  } catch (err) {
476
550
  return failure(err);
477
551
  }
@@ -496,10 +570,18 @@ registerTool(
496
570
  try {
497
571
  const session = findClaudeSession(target, { desktopOnly });
498
572
  if (!session) return textResult(`No live Claude session matches "${target}".`, true);
573
+ const transcriptRoot = rootPolicy.enabled ? rootPolicy.capture(session.cwd, "Claude transcript working directory") : null;
574
+ assertSessionRoot(session);
499
575
  if (desktopOnly) {
500
576
  const verified = withDesktopContext(session);
501
577
  if (verified.desktop?.status !== "matched") throw preflightFailure("CLAUDE_DESKTOP_TASK_UNVERIFIED", verified.desktop?.reason ?? "This task is not in the signed-in Claude account.");
502
578
  }
579
+ if (transcriptRoot) {
580
+ const current = findClaudeSession(session.sessionId ?? String(session.pid), { desktopOnly });
581
+ if (!current || current.pid !== session.pid || current.socket !== session.socket) throw new Error("The Claude transcript target changed before it could be read");
582
+ rootPolicy.recheck(transcriptRoot, "Claude transcript working directory");
583
+ if (desktopOnly) assertClaudeSessionProcess(current);
584
+ }
503
585
  const { file, messages } = readTranscript(session.sessionId, session.cwd, limit ?? 10);
504
586
  if (!messages.length) return textResult(`No transcript entries found (looked at ${file}).`);
505
587
  const body = messages.map((m) => `[${m.role}] ${m.text}`).join("\n\n");
@@ -563,7 +645,11 @@ registerTool(
563
645
  const sessions = listClaudeSessions().filter((s) => s.pid !== process.pid);
564
646
  const accounts = readBridgeAccounts();
565
647
  const eligible = sessions.filter((session) => !desktopOnly || session.entrypoint === "claude-desktop")
648
+ .filter((session) => !rootPolicy.enabled || rootPolicy.allows(session.cwd))
566
649
  .filter((session) => !desktopOnly || withDesktopContext(session, accounts.claude).desktop?.status === "matched");
650
+ const visibleInbox = peer.inbox.filter((record) => recordVisible(record, accounts));
651
+ const visiblePending = [...peer.pendingMessages.keys()].filter((id) => readReceipt(id));
652
+ const visibleForwarding = replyForwarder.status((record) => recordVisible(record, accounts));
567
653
  const sender = desktopOnly ? readCodexSenderContext(extra?._meta) : null;
568
654
  const lines = [
569
655
  `platform: ${PLATFORM_LABEL} (${process.platform}/${process.arch})`,
@@ -578,17 +664,17 @@ registerTool(
578
664
  `Claude account: ${accounts.claude.status}${accounts.claude.fingerprint ? ` (${accounts.claude.fingerprint.slice(0, 12)})` : ` - ${accounts.claude.reason}`}`,
579
665
  `Codex account: ${accounts.codex.status}${accounts.codex.fingerprint ? ` (${accounts.codex.fingerprint.slice(0, 12)})` : ` - ${accounts.codex.reason}`}`,
580
666
  `live sessions: ${eligible.length}`,
581
- `excluded: ${sessions.length - eligible.length} non-Desktop or inactive-account session(s)`,
667
+ ...(rootPolicy.enabled ? [] : [`excluded: ${sessions.length - eligible.length} non-Desktop or inactive-account session(s)`]),
582
668
  `relay thread: ${forwarding.threadId ?? "(none - use bind_codex_thread)"}`,
583
669
  `delivery: ${delivery.describe()}`,
584
- `inbox: ${peer.inbox.length} pending message(s)`,
585
- `outstanding: ${peer.pendingMessages.size} message(s) awaiting receipt or reply`,
586
- `reply forwarding: ${JSON.stringify(replyForwarder.status())}`,
587
- ...[...peer.pendingMessages.keys()].map((id) => `pending message: ${id} (${peer.readDelivery(id)?.status ?? "sent_unconfirmed"})`),
670
+ `inbox: ${visibleInbox.length} pending message(s)`,
671
+ `outstanding: ${visiblePending.length} message(s) awaiting receipt or reply`,
672
+ `reply forwarding: ${JSON.stringify(visibleForwarding)}`,
673
+ ...visiblePending.map((id) => `pending message: ${id} (${peer.readDelivery(id)?.status ?? "sent_unconfirmed"})`),
588
674
  ];
589
675
  const state = runtime.status();
590
676
  lines.push(`runtime pid: ${state.pid}`, `loaded source: ${state.revision}`, `disk source: ${state.diskRevision ?? "unreadable"}`, `runtime state: ${state.current ? "current" : `STALE - ${state.reason}; reconnect this MCP server in the existing task`}`);
591
- return { ...textResult(lines.join("\n"), !state.current), structuredContent: { runtime: state, accounts: { claude: publicAccountState(accounts.claude), codex: publicAccountState(accounts.codex) }, replyForwarding: replyForwarder.status(), ...(sender ? { sender } : {}) } };
677
+ return { ...textResult(lines.join("\n"), !state.current), structuredContent: { runtime: state, accounts: { claude: publicAccountState(accounts.claude), codex: publicAccountState(accounts.codex) }, replyForwarding: visibleForwarding, ...(sender ? { sender } : {}) } };
592
678
  } catch (err) {
593
679
  return failure(err);
594
680
  }
@@ -23,6 +23,19 @@ function uniqueLiveSessions(sessions) {
23
23
  return [...unique.values()];
24
24
  }
25
25
 
26
+ export function sameClaudeDesktopRecipient(selected, current) {
27
+ return Boolean(
28
+ selected && current &&
29
+ selected.pid === current.pid &&
30
+ typeof selected.sessionId === "string" && selected.sessionId.length > 0 &&
31
+ selected.sessionId === current.sessionId &&
32
+ typeof selected.socket === "string" && selected.socket.length > 0 &&
33
+ selected.socket === current.socket &&
34
+ typeof selected.processStart === "string" && selected.processStart.length > 0 &&
35
+ selected.processStart === current.processStart
36
+ );
37
+ }
38
+
26
39
  export function resolveClaudeDesktopSession({ target, expectedCwd, expectedTaskId, sessions, account, readContext }) {
27
40
  if (account?.status !== "verified") {
28
41
  throw preflightFailure("CLAUDE_ACCOUNT_UNVERIFIED", account?.reason ?? "The current Claude Desktop account is not confirmed.");
@@ -53,6 +66,9 @@ export function resolveClaudeDesktopSession({ target, expectedCwd, expectedTaskI
53
66
  if (matches.length > 1) {
54
67
  throw preflightFailure("CLAUDE_SESSION_AMBIGUOUS", "The exact target identifies multiple live sessions; use an unambiguous sessionId or pid.");
55
68
  }
69
+ if (!matches.length) {
70
+ throw preflightFailure("CLAUDE_SESSION_NOT_FOUND", "No live Claude Desktop session matches the exact target. Reinspect the existing task and use its current sessionId or pid.");
71
+ }
56
72
  if (matches.length === 1) {
57
73
  const found = matches[0];
58
74
  if (found.entrypoint !== "claude-desktop") {
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  import { runSupervisor } from "./mcp-supervisor.mjs";
3
- await runSupervisor("index.mjs");
3
+ import { handleTelemetryCommand } from "./telemetry.mjs";
4
+ if (!await handleTelemetryCommand(process.argv.slice(2))) await runSupervisor("index.mjs");