relay init: start the desktop app's core before the public one
What changes
relay init(the message-injection setup path): when the Agent Relay desktop app is installed and no core answers on the bridge socket, init first asks the desktop app to start its own core and attaches to it. "No core answers" includes the case where init has just retired its own outdated core.- macOS:
open -g -j -b com.agentrelay.desktop(background, hidden, no focus steal). The app registers and starts its core LaunchAgent (com.agentrelay.desktop.core) as it does on every launch. - Linux:
systemctl --user start agent-relay.servicewhensystemctl --user is-enabled --quiet agent-relay.servicesucceeds. Otherwise, only in a desktop session (DISPLAYorWAYLAND_DISPLAYset), a runnableagent-relay-desktoplauncher (/usr/bin, then per-user updater trees, newest first) is started detached with--background(tray). It is never started with--headless: a headless start removes the person's GUI autostart entry, so arelay initover SSH into their desktop would cost them the tray app at login. With no enabled unit and no display, init skips the desktop start and falls back to the public core with the warning (reason: the desktop app's core needsagent-relay.serviceenabled, or a desktop session). - Windows: no desktop start, so behaviour is unchanged. The logon task is off unless the person turned it on.
- A moved socket (
AGENT_RELAY_CORE_SOCKETset to anything other than the default) also skips the desktop start, because the desktop core never serves it.
- macOS:
- After asking the desktop app, init shows
Asked the Agent Relay desktop app to start its core; using the core now running on this machine (agent-relay-probe <v>, pid <n>).Nocore.jsonis written for it. PROBE_VERSIONgoes from 15.0.7 to 15.0.8. The Cursor row indocs/probe-move.mdnow says the editor route needs core 15.0.8 or newer.
Fallback rule
desktopInstalled can be stale (markers left after an uninstall, or a disabled service), so a failed desktop start never fails init. The desktop start counts as failed if the start command throws or exits non-zero, or if no core answers within CORE_START_TIMEOUT_MS (30s, wall-clock). In that case init installs and starts the public core exactly as before. Once the public core is up, init prints one warning as a ! line in the message-injection summary, so it shows in plain output and also in the guided flow's note after the spinner stops:
! The Agent Relay desktop app is installed, but its core could not be started (). relay init started the public Agent Relay core instead. To use the desktop app's core, open the Agent Relay desktop app, then run relay init --stop and relay init again.
If the public core fails too, the desktop failure is added to the usual "Message injection is not available (...)" reason.
The existing "one core per machine; attach, never double" rule still holds:
- A core that is already running is attached to. The desktop app is never asked, and nothing is stopped.
- A core that answers after the probe download, or that takes the socket while the public core starts, is attached to. Only the process this run spawned is stopped (new: before this PR it was left to exit on the socket lock). The core that answered is never stopped.
- After a switch off the local workspace, a desktop core this run asked for gets running sessions moved to the new workspace, the same as a public core this run started.
--uninstall's temporary core still uses today's path. It never opens the desktop app.
Tests
init.test.ts, newstarting the desktop app coreblock (fake deps only):- desktop installed and no core: the desktop start is attempted, its core is attached, there is no probe download and no
core.json - the desktop start throws (including Linux with no enabled unit and no display), or its core never answers: the public core starts,
core.jsonrecords it, and the warning prints exactly once - desktop not installed: the desktop start is never called (unchanged path)
- a desktop core appears while the public core starts: it is attached, our pid 5151 is stopped, the foreign pid 777 is never stopped, there is no
daemon.shutdownand nocore.json - a desktop core appears during the download: it is attached and
startCoreis never called - a foreign core is already running: it is untouched and the desktop app is not asked
- the public core also fails: one reason names both failures
- init's own outdated core is retired: the desktop app is asked next
- a switch to a team with a desktop core this run asked for: sessions are moved
- desktop installed and no core: the desktop start is attempted, its core is attached, there is no probe download and no
desktop-presence.test.ts: which commandstartDesktopCorepicks per platform, with fake effects (macOS open and its failure, Linux enabled unit, launcher--backgroundwithDISPLAYorWAYLAND_DISPLAY, no unit and no display throws without launching anything, newest per-user tree, nothing runnable found, Windows runs nothing).- The setup default in
init.test.tsfakesstartDesktopCoreto return undefined. Tests also already stubAGENT_RELAY_CORE_SOCKET, so the real default would skip the desktop start as well. - CLI typecheck and lint pass (no new warnings;
ensureCorecomplexity is back to main's 16). The whole CLI suite passes: 2684 passed, 17 skipped.ci-standalone-smokehit its 5s timeout once under full-suite load and passes on its own.
Not covered
-
Merge after
probe-v15.0.8is published. Until it exists,relay initon a machine with no core binary cannot download the pinned core and falls back to MCP delivery. For the same reason, the Flows v2scenario-core-init-paritystep fails on this PR, because that lane installs the pinned release. -
CI never runs a real desktop start (no app, launchd or systemd), and neither did I by hand. The exact commands are only checked against fake effects.
-
The macOS LaunchAgent can wait on a Login Items approval prompt. In that case init falls back after 30s with the warning.
-
On Linux without an enabled
agent-relay.serviceand without a display (a headless server, or SSH into a desktop), init never starts the desktop core; it falls back to the public core with the warning. The desktop's headless launcher is deliberately not used, because of its autostart side effect.
🤖 Generated with Claude Code
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Agent Relay sessions
claudesessiona994d8be· opened viagh pr create· last active 2026-11-03