You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A Claude Desktop (or other desktop-app-hosted MCP client) user gets either a
documented, supported FocusRelay setup path, or a documented, evidence-backed
"not supported yet" with a tracked follow-up — instead of a silent bridge
timeout. The spike's deliverable is a decision, not code: an updated
transport decision document and, if warranted, a follow-up implementation
issue.
Validation impact
docs — the spike produces diagnostic evidence and an updated decision
document only. No production code changes; any implementation happens under
a follow-up issue with its own (likely transport-reliability) impact.
Current evidence
FocusRelay works under terminal-launched MCP hosts (Claude Code, OpenCode)
and fails under Claude Desktop on macOS 26/27: the server starts and
handshakes, but bridge round trips never complete.
Verified on a failing machine: Claude Desktop's app bundle is not
sandboxed (no com.apple.security.app-sandbox entitlement), while
OmniFocus 4 is. The failure layer is therefore not app-sandbox
inheritance but macOS TCC App Data protection
(kTCCServiceSystemPolicyAppData, macOS 14+) over another app's ~/Library/Containers — which is exactly where the FocusRelay IPC
rendezvous lives, because Omni Automation URL-scheme scripts can only
write inside OmniFocus's own container.
The grant follows the requesting binary's code identity, and Full Disk
Access does not substitute (SystemPolicyAllFiles ≠ SystemPolicyAppData).
Direct architectural analog: omnifocus-operator (same
container-rendezvous design) has this exact bug open — HelloThisIsFlo/omnifocus-operator#2.
Their diagnosis: ad-hoc or self-signed identities get the grant recorded
with auth_value=5 (not honored), causing endless re-prompts or denials;
their planned fix is a Developer ID-signed + notarized launcher.
Corroboration from Claude Desktop MCP issues: anthropics/claude-code#27557, #36832, #63032, #66216 —
App Data prompts fire but grants don't persist through unsigned helper
chains; Apple Events (osascript) remains promptable and functional.
Competitors using osascript/JXA with stdout return paths
(themotionmachine/OmniFocus-MCP, jqlts1/omnifocus-mcp-enhanced,
vitalyrodnenko/OmnifocusMCP) report zero permission-wall issues:
their data path never crosses the OmniFocus container boundary.
Constraint: the roadmap Standing Decision (finalized in Consolidate on plugin-url and move pure JXA out of the shipped runtime #80, docs/transport-decision-2026-03-13.md) says plugin URL dispatch through
the Bridge plugin is the only architecture. This spike explicitly
revisits that decision with new evidence; the decision doc is the
deliverable that either reaffirms or amends it.
Prerequisite
Options 1 and 2 below require an Apple Developer Program membership
(US$99/year) for Developer ID signing and notarization. Task 1 (the TCC
diagnostic) is free and must run first — its result determines whether
signing can help at all, before any money is spent. Option 3 requires no
signing.
Proposed scope (ordered, with kill/keep gates)
TCC attribution diagnostic (free, mandatory first). Under Claude
Desktop, trigger a bridge call while capturing log stream --predicate 'subsystem == "com.apple.TCC"' and classify the
recorded auth_value for the requesting identity. The critical unknown:
is the focusrelay child process attributed to its own code identity or
to the host app's responsible process? This gates everything after it.
Option 1 — Developer ID sign + notarize the existing Homebrew binary.
Keep if the App Data grant is honored (auth_value=2, prompt names
FocusRelay): zero architecture change, zero hot-path cost. Kill if
responsible-process attribution routes the grant to the host app.
Includes pipeline scoping: codesign + notarytool + stapling in scripts/package-release.sh, and whether the tap tarball
(deverman/homebrew-focus-relay) preserves signature and notarization
through download/quarantine.
Option 2 — signed LaunchAgent broker (only if Option 1 dies). A
launchd-managed singleton owns the IPC client under its own TCC
identity; focusrelay serve becomes a thin stdio shim. Also solves
multi-host contention (cf. themotionmachine/OmniFocus-MCP#80
arriving at the same launchd-singleton idea). Installer/uninstaller UX
folds into Add guided setup for Homebrew-installed FocusRelay #92 guided setup — no second installer path. Kill on
unacceptable setup friction; a background agent users don't understand
is a real adoption cost.
Option 3 — osascript/JXA stdout return path (Desktop-only degraded
mode). Competitor-proven under Claude Desktop; still executes OmniJS
inside OmniFocus (documented-API contract holds); requires no signing.
Conflicts with the Consolidate on plugin-url and move pure JXA out of the shipped runtime #80 Standing Decision and adds per-request osascript
spawn cost — evaluate strictly as a documented fallback for hosts where
the container is unreachable, never the terminal-client path.
Decision doc + follow-up. Update docs/transport-decision-2026-03-13.md
(or successor) with: verbatim TCC log evidence (usernames stripped), codesign -dv/spctl -a output per tested identity, a host × option
result matrix, terminal-path latency proof-of-no-change, and an explicit
stands-or-amended verdict on the Standing Decision. File the follow-up
implementation issue if any option survives.
Performance budget
The terminal-client hot path (Claude Code, OpenCode, Codex CLI) must be
code-path-identical before and after any multi-host change. Zero
per-request regression is tolerated; Desktop enablement may only add cost
to a Desktop-specific path. Canary-profile before/after evidence on the
unchanged terminal path is required in the follow-up issue.
Safety and compatibility
No production code changes in this spike; no release invalidation.
Diagnostic steps read TCC state and logs only; no TCC.db modification.
Depends on the IPC hardening issue landing first: hardening defines the
on-disk contract and permissions any broker or signed binary would own.
Add guided setup for Homebrew-installed FocusRelay #92 guided setup should not finalize its installer flow until this
spike's signing verdict lands, since a Developer ID pipeline (or a
LaunchAgent) directly shapes it.
Acceptance criteria
TCC diagnostic evidence captured and classified (service, identity,
attribution, auth_value), with usernames stripped from paths.
Each option has an explicit keep/kill verdict backed by recorded
evidence; untested options state why.
Transport decision doc updated with the evidence list above and an
explicit verdict on the Standing Decision.
Follow-up implementation issue filed (or an explicit "no viable path;
revisit when X changes" recorded).
No code merged from this spike.
Non-goals
Implementing any transport change.
Changing the default plugin-URL architecture for terminal clients.
User outcome
A Claude Desktop (or other desktop-app-hosted MCP client) user gets either a
documented, supported FocusRelay setup path, or a documented, evidence-backed
"not supported yet" with a tracked follow-up — instead of a silent bridge
timeout. The spike's deliverable is a decision, not code: an updated
transport decision document and, if warranted, a follow-up implementation
issue.
Validation impact
docs— the spike produces diagnostic evidence and an updated decisiondocument only. No production code changes; any implementation happens under
a follow-up issue with its own (likely
transport-reliability) impact.Current evidence
and fails under Claude Desktop on macOS 26/27: the server starts and
handshakes, but bridge round trips never complete.
sandboxed (no
com.apple.security.app-sandboxentitlement), whileOmniFocus 4 is. The failure layer is therefore not app-sandbox
inheritance but macOS TCC App Data protection
(
kTCCServiceSystemPolicyAppData, macOS 14+) over another app's~/Library/Containers— which is exactly where the FocusRelay IPCrendezvous lives, because Omni Automation URL-scheme scripts can only
write inside OmniFocus's own container.
Access does not substitute (
SystemPolicyAllFiles≠SystemPolicyAppData).container-rendezvous design) has this exact bug open —
HelloThisIsFlo/omnifocus-operator#2.
Their diagnosis: ad-hoc or self-signed identities get the grant recorded
with
auth_value=5(not honored), causing endless re-prompts or denials;their planned fix is a Developer ID-signed + notarized launcher.
anthropics/claude-code#27557,
#36832,
#63032,
#66216 —
App Data prompts fire but grants don't persist through unsigned helper
chains; Apple Events (osascript) remains promptable and functional.
osascript/JXA with stdout return paths(themotionmachine/OmniFocus-MCP, jqlts1/omnifocus-mcp-enhanced,
vitalyrodnenko/OmnifocusMCP) report zero permission-wall issues:
their data path never crosses the OmniFocus container boundary.
docs/transport-decision-2026-03-13.md) says plugin URL dispatch throughthe Bridge plugin is the only architecture. This spike explicitly
revisits that decision with new evidence; the decision doc is the
deliverable that either reaffirms or amends it.
Prerequisite
Options 1 and 2 below require an Apple Developer Program membership
(US$99/year) for Developer ID signing and notarization. Task 1 (the TCC
diagnostic) is free and must run first — its result determines whether
signing can help at all, before any money is spent. Option 3 requires no
signing.
Proposed scope (ordered, with kill/keep gates)
Desktop, trigger a bridge call while capturing
log stream --predicate 'subsystem == "com.apple.TCC"'and classify therecorded
auth_valuefor the requesting identity. The critical unknown:is the
focusrelaychild process attributed to its own code identity orto the host app's responsible process? This gates everything after it.
Keep if the App Data grant is honored (auth_value=2, prompt names
FocusRelay): zero architecture change, zero hot-path cost. Kill if
responsible-process attribution routes the grant to the host app.
Includes pipeline scoping:
codesign+notarytool+ stapling inscripts/package-release.sh, and whether the tap tarball(deverman/homebrew-focus-relay) preserves signature and notarization
through download/quarantine.
launchd-managed singleton owns the IPC client under its own TCC
identity;
focusrelay servebecomes a thin stdio shim. Also solvesmulti-host contention (cf.
themotionmachine/OmniFocus-MCP#80
arriving at the same launchd-singleton idea). Installer/uninstaller UX
folds into Add guided setup for Homebrew-installed FocusRelay #92 guided setup — no second installer path. Kill on
unacceptable setup friction; a background agent users don't understand
is a real adoption cost.
mode). Competitor-proven under Claude Desktop; still executes OmniJS
inside OmniFocus (documented-API contract holds); requires no signing.
Conflicts with the Consolidate on plugin-url and move pure JXA out of the shipped runtime #80 Standing Decision and adds per-request osascript
spawn cost — evaluate strictly as a documented fallback for hosts where
the container is unreachable, never the terminal-client path.
docs/transport-decision-2026-03-13.md(or successor) with: verbatim TCC log evidence (usernames stripped),
codesign -dv/spctl -aoutput per tested identity, a host × optionresult matrix, terminal-path latency proof-of-no-change, and an explicit
stands-or-amended verdict on the Standing Decision. File the follow-up
implementation issue if any option survives.
Performance budget
The terminal-client hot path (Claude Code, OpenCode, Codex CLI) must be
code-path-identical before and after any multi-host change. Zero
per-request regression is tolerated; Desktop enablement may only add cost
to a Desktop-specific path. Canary-profile before/after evidence on the
unchanged terminal path is required in the follow-up issue.
Safety and compatibility
on-disk contract and permissions any broker or signed binary would own.
spike's signing verdict lands, since a Developer ID pipeline (or a
LaunchAgent) directly shapes it.
Acceptance criteria
attribution, auth_value), with usernames stripped from paths.
evidence; untested options state why.
explicit verdict on the Standing Decision.
revisit when X changes" recorded).
Non-goals