Commit 831454b
authored
fix(context-for-claude): stop reporting a Claude Desktop that cannot answer as connected (#11863)
## What was wrong
Context for Claude told users it was connected to Claude Desktop by
reading a config file. On this Mac that claim was false for three days
and nothing anywhere said so.
A pre-1.0.11 developer build had written its own build directory into
`claude_desktop_config.json`. The directory was deleted; the entry
stayed. From 2026-08-16 to 2026-08-19 Claude Desktop tried to spawn a
binary that was not there at every launch:
```
2026-08-16T17:51:54 [context-for-claude] Using MCP server command:
/Users/…/omi-worktrees/ctx-release/desktop/context-for-claude/build/Context for Claude.app/…
Failed to spawn process: No such file or directory
```
For all three days the menu bar and the Agents pane both read
**Connected to Claude Code and Claude Desktop**, because the only
question either asked was whether an entry existed in a file.
`ClaudeRegistrar.status()` reads two config files, and that was the
whole of what the user-facing surfaces knew.
`ClaudeServerLiveness` has been able to answer the real question — is a
server of ours alive inside the running Claude Desktop — since it was
written. It was reachable only from `ClaudeHandoff`, which the tutorial
runs once.
## Two commits
**1. Attribute a server to its nearest Claude, not to any Claude above
it.**
The probe walked up looking for a Claude Desktop PID *anywhere* in a
server's ancestry. Claude Code now runs inside Claude Desktop, so every
Claude Code session in the desktop app puts one there:
```
context-for-claude-mcp
→ ~/Library/Application Support/Claude/claude-code/2.1.229/…/claude
→ /Applications/Claude.app/Contents/Helpers/disclaimer
→ /Applications/Claude.app/Contents/MacOS/Claude ← a Claude Desktop PID
```
Its own comment says one of Claude Code's servers must never be read as
proof that Claude Desktop has one; the walk defeated it. On the Mac
above, the probe answered `servingClaudeDesktop` on the strength of
fifteen Claude Code servers while Claude Desktop had none — so wiring
the surfaces to it without this fix would have left them lying on
exactly the setup that had the bug.
The first owner met now wins. `unknown` and `none` keep their existing
meanings, so the tutorial's "never quit an app on a lookup that failed"
doctrine is untouched.
**2. Wire liveness into the two surfaces that report the connection.**
`ClaudeConnection` joins registration (disk) to reachability (process
table). A registration Claude Desktop has not picked up is no longer
called connected, and both surfaces name the only thing that fixes it.
| state | before | after |
|---|---|---|
| registered, server live | Connected to Claude Code and Claude Desktop
| unchanged |
| registered, no server | Connected to Claude Code and Claude Desktop |
Connected to Claude Code. Quit and reopen Claude Desktop to finish
connecting it. |
| Claude Desktop not running / unreadable | Connected … | unchanged —
`.unknown` is never nagged on |
The `Connect` press is withheld in the needs-restart state: it rewrites
two files that are already correct and would change nothing the user can
see. The app still does not quit anybody's Claude — `ClaudeHandoff` may
offer that with consent because it is about to hand Claude a question; a
status line has no such errand. The remedy is a sentence.
`ClaudeDesktopProcesses` becomes the one place the running Claude
Desktops are read, so `ClaudeRegistrar` stays free of AppKit and the
three call sites that built the same `runningApplications()` query
cannot disagree about the bundle identifier.
## Verification
- `swift test` in `desktop/context-for-claude`: **1655 tests, 9 skipped,
0 failures**.
- **Live, on a Mac in exactly the broken state** (registered for both
surfaces, Claude Desktop running since Aug 14 with no server of its
own), driving the shipping value types off the real config files and the
real process table:
```
connection = (claudeCode: true, claudeDesktop: true,
desktopNeedsRestart: true)
menu bar = "Connected to Claude Code"
+ "Quit and reopen Claude Desktop to finish connecting it."
settings = "Connected to Claude Code. Quit and reopen Claude Desktop to
finish connecting it."
```
With the pre-fix behaviour restored, the same probe prints `Connected to
Claude Code and Claude Desktop.` over the dead connector.
- **Mutation checks**, both run:
- reverting `desktopIsReachable` to plain registration fails 6 of the 10
new `ClaudeConnectionTests`;
- restoring the shipped ancestry walk fails
`testAClaudeCodeServerInsideTheDesktopAppIsNotTheDesktopApps` with
`("claudeDesktop") is not equal to ("claudeCode")`.
- **Ran the real app.** Built the bundle, launched it, and opened the
menu-bar popover. It draws:
> **Connected to Claude Code**
> Quit and reopen Claude Desktop to finish connecting it.
with the note on its own line, no `Connect` button, and no overlap in
the connection block. The dev build registered under
`context-for-claude-dev` and left the release's entry untouched, so
#11712's split still holds with this change on top of it.
- `StatusPopoverLayoutTests` now sweeps liveness, because
`notServingClaudeDesktop` adds a fourth line to the popover's connection
block in the state that also carries the longest summary — the tallest
the block can get, and the one the panel must be sized by.
Note for reviewers: `desktop/context-for-claude` has no Swift lane in
CI, so the suite above was run locally and is not re-run by any check on
this PR.
## Product invariants affected
none
## Failure class (fixes)
Failure-Class: FC-platform-grant-record-as-live-capability
Second instance in this app, same structure as the first (#10783): a
persisted record was treated as proof of a live capability whose rights
are actually bound at another process's start. There it was a TCC grant
read back as `true` while ScreenCaptureKit refused the same process;
here it is an `mcpServers` entry read back as connected while Claude
Desktop, which reads that file only at its own launch, has no server.
The class's canonical prevention already prescribes the shape used here
— model the capability as granted / denied / granted-but-stale, resolve
it from the platform record *and* from what the subsystem that does the
work reports, and surface relaunch as the remedy rather than retrying.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/BasedHardware/omi/pull/11863?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->10 files changed
Lines changed: 570 additions & 56 deletions
File tree
- desktop/context-for-claude
- Sources/ContextApp
- Integration
- MenuBar
- Settings
- Tutorial
- Tests/ContextAppTests
- changelog/unreleased
Lines changed: 88 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
Lines changed: 89 additions & 23 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
477 | 477 | | |
478 | 478 | | |
479 | 479 | | |
480 | | - | |
481 | | - | |
482 | | - | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
483 | 484 | | |
484 | 485 | | |
485 | 486 | | |
| |||
495 | 496 | | |
496 | 497 | | |
497 | 498 | | |
498 | | - | |
499 | | - | |
500 | | - | |
501 | | - | |
502 | | - | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
503 | 515 | | |
504 | 516 | | |
505 | 517 | | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
506 | 587 | | |
507 | 588 | | |
508 | 589 | | |
| |||
530 | 611 | | |
531 | 612 | | |
532 | 613 | | |
533 | | - | |
534 | | - | |
535 | | - | |
536 | | - | |
537 | | - | |
538 | | - | |
539 | | - | |
540 | | - | |
541 | | - | |
542 | | - | |
543 | | - | |
544 | | - | |
545 | | - | |
546 | | - | |
547 | | - | |
548 | 614 | | |
549 | 615 | | |
550 | 616 | | |
| |||
Lines changed: 17 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
201 | 201 | | |
202 | 202 | | |
203 | 203 | | |
204 | | - | |
205 | | - | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
206 | 210 | | |
207 | 211 | | |
208 | 212 | | |
209 | 213 | | |
210 | 214 | | |
211 | | - | |
212 | | - | |
213 | | - | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
214 | 226 | | |
215 | 227 | | |
216 | 228 | | |
| |||
Lines changed: 16 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
46 | 49 | | |
47 | 50 | | |
48 | 51 | | |
| |||
512 | 515 | | |
513 | 516 | | |
514 | 517 | | |
515 | | - | |
516 | | - | |
517 | | - | |
518 | | - | |
519 | | - | |
| 518 | + | |
520 | 519 | | |
521 | 520 | | |
522 | 521 | | |
| |||
528 | 527 | | |
529 | 528 | | |
530 | 529 | | |
531 | | - | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
532 | 540 | | |
533 | 541 | | |
534 | 542 | | |
| |||
651 | 659 | | |
652 | 660 | | |
653 | 661 | | |
654 | | - | |
| 662 | + | |
655 | 663 | | |
656 | 664 | | |
657 | 665 | | |
| |||
0 commit comments