fix(init): home-folder pin guard, Pi banner, probe copy, OpenCode dirs, Grok project scope

Merged
KJ
9d ago
+906-26

Five small relay init / probe-core fixes found in end-to-end testing. Each one was reproduced first with an isolated HOME, AGENT_RELAY_HOME, XDG dirs and AGENT_RELAY_CORE_SOCKET.

1. Home-folder guard (the important one)

Root cause. There were two problems:

  • relay init in ~ resolved the project to ~, so it wrote ~/.agentworkforce/relay/workspace-key.json into the relay home. That folder also holds workspaces.json, cloud-auth.json, fleet files and binaries. Reproduced: workspace-key.json showed up next to workspaces.json.
  • findProjectRoot counts .agentworkforce/relay as a project marker, and every user has that folder in ~. So any plain folder under ~ (no git or package marker) also resolved to ~ (reproduced: ~/notes -> ~). Once a stray pin is in ~, the pin check matches first for every such folder.

Fix.

  • @agent-relay/config gets relayHomeDir, isRelayHomeDir, assertNotRelayHome and RelayHomeProjectError. A data folder counts as a relay home when either:
    • it is AGENT_RELAY_HOME (default ~/.agentworkforce/relay), compared as canonical paths: symlinks resolved, and the same dev/inode when both exist; or
    • it holds a workspaces.json, which no project folder does. This catches the real home's relay folder when HOME is isolated.
  • findProjectRoot no longer treats a relay home as a marker or as a pinned subproject.
  • All pin writers go through withProjectWorkspaceKeyLock, which now asserts first. That covers writeProjectWorkspaceKey, ...PreservingSession, pinProjectWorkspaceSession, enrollment pins, node up and broker-lifecycle. writeProjectWorkspaceTargetIfSelectionCurrent is best-effort, so it returns false instead of throwing.
  • relay init checks before it starts any server or writes anything. The error says: "Run this inside a project folder (cd into the project first), or set AGENT_RELAY_PROJECT to the project folder."
  • No --project flag was added. AGENT_RELAY_PROJECT already does this job.
  • persistWorkspaceSession with a name and the relay home as the project folder skips the pin and still updates the machine-wide store. So relay workspace switch X from ~ keeps working, without a pin. A bare key, which only ever pins a project, is refused.

2. Pi extension banner

Root cause. The banner always said "Enable Receives messages for this session in Agent Relay", even when auto_activate and pi_auto_activate (both turned on by relay init --tools pi) make the session join by itself.

Fix.

  • The core keeps ~/.agentworkforce/desktop/pi-auto-join in sync on every reconcile pass. The marker exists when a workspace is set and activation_applies(Pi) is true. It is written only where Pi has run and is removed otherwise.
  • The extension reads the marker at session_start. When it is there, the banner says "This session joins Agent Relay automatically and receives messages."
  • On the desktop, where Pi auto-activation is off by default, there is no marker and the text is unchanged.

3. ~/.local/bin/agent-relay-probe after uninstall

Root cause. account::with_account gives every production core a command_line_copy. That includes the --cli-managed core that relay init starts, so it copied itself to ~/.local/bin. Reproduced: the copy was byte-identical to $AGENT_RELAY_HOME/bin/agent-relay-probe.

Fix.

  • with_account takes a command_line_copy flag, and daemon::serve passes !cli_managed. The desktop is unchanged.
  • relay init still pins core 15.0.3 until the next core release. So a full relay init --uninstall also removes ~/.local/bin/agent-relay-probe when it is a regular file, byte-for-byte equal to the core init installs. A symlink, any other build (such as the desktop's) and a partial uninstall (--scope project or --tools) all leave it alone.

4. Empty ~/.config/opencode

Root cause. It is not the CLI. The core's OpenCode harness detect() runs opencode --version, and OpenCode creates its XDG config, data, cache and state folders on every start, --version included. Confirmed by running opencode --version alone under an empty HOME.

Fix.

  • system_version runs the probe with XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME and XDG_STATE_HOME pointed at a throwaway tempdir, which is removed afterwards. The version output is unchanged.
  • This takes effect with the next core release that relay init pins.
  • I did not add an uninstall cleanup for folders an older core already created. Those folders are not empty (~/.cache/opencode/version), and init cannot tell them apart from a real OpenCode install.

5. Real ~/.grok/config.toml read during uninstall

Root cause. Same as item 1. The test project sat under the real home, and the real home's .agentworkforce/relay made it the project root. So the project-scope path <projectRoot>/.grok/config.toml was the real ~/.grok/config.toml. Reproduced: "Grok CLI left as is: .grok/config.toml … /realhome/.grok/config.toml".

Fix.

  • The project root is now resolved correctly (item 1), so the real file is no longer looked at.
  • relay init --uninstall in a folder whose project is a relay home skips the project scope. --scope project there says there is no project.
  • Reading the file was otherwise harmless: uninstall only removes entries that carry init's managed marker, and an unmarked one is reported as "left as is". It still should not look outside the selected scope, so this is now fixed.

Tests

  • New tests:
    • config: the relay home is not a marker; workspaces.json detection; symlinked and not-yet-created paths.
    • cloud: every pin writer refuses the relay home and writes nothing.
    • workspace-session: a named switch from ~ writes no pin; a bare key is refused.
    • init: refuses in ~ and through a symlink, starting nothing; uninstall in ~ skips the project scope; the probe copy is removed or kept as described in item 3.
    • Pi extension.test.mjs banner test, and a Rust test for the marker sync.
    • OpenCode version-probe isolation test.
    • The with_account test checks the copy is set for the desktop and not for a cli-managed core.
  • Vitest (packages/cli, packages/config, packages/cloud): the last full local run before the final merges had 3 failures out of 3227. ci-standalone-smoke and two agent-relay-mcp.autoname tests timed out under load. Other full runs also failed integration-relayfile-contract and a PTY replay test. ci-standalone-smoke, integration-relayfile-contract and the replay test's folder passed when rerun alone.
  • agent-relay-mcp.autoname timeouts were partly caused by this PR, and failed the macOS test job at 5cea9561a. isRelayHomeDir canonicalized missing paths, which is slow on macOS's automounted /home (about 170 ms per call locally). Fixed by skipping the check when no pin was read and by not walking missing paths when one side exists; a canonical result for two missing paths is cached per process. That fix is inside merge commit b5b68f8ba. Side effect: if a missing ancestor later becomes a symlink in the same process, the cached result is stale.
  • tsc --noEmit passes for cli, cloud and config. Lint has 0 errors.
  • Probe, run with an empty HOME as CI does:
    • default features: lib 2602 passed. history_flush::a_failing_receiver_ends_at_the_deadline_with_records_pending failed in both local runs, and destination::consecutive_sends_share_a_connection_and_reread_a_rotated_session_on_401 failed in one of two. Both are outside the code this PR changes, but neither was compared against main locally.
    • Later runs covered only relay_agents (114 passed in both feature configs) and targeted lib tests, not the whole suite.
    • --no-default-features --features production-binaries: all passed.
  • E2E in an isolated HOME:
    • init in ~ refuses and writes nothing.
    • init in ~/notes pins ~/notes.
    • uninstall removes the stale ~/.local/bin copy.
    • the "real home" uninstall scenario no longer reads its .grok/config.toml.

Stays out of the init --hosted join command and Codex session-start areas, which are being changed in parallel.

Takes effect later

Items 2, 3 (the stop-copying half) and 4 are core changes. relay init still installs core 15.0.3, so they reach users only after a core release and a PROBE_VERSION bump.

CI notes

  • E2E Failed to start broker: The operation was aborted due to timeout hit Ubuntu at 9946ed6ad and macOS at b5b68f8ba. Local --daemon-only passed and both jobs passed at 93a424d4b, but the root cause was not established. The message matches the AbortSignal.timeout in packages/harness-driver/src/transport.ts.
  • relay_agents::auto_activation_puts_new_live_sessions_on_the_relay failed on macOS at 93a424d4b. This PR adds a store read to that loop. The test now waits for the store, which treats a suspected read-after-registration race; it could not be reproduced locally (15/15 passed before the change).

🤖 Generated with Claude Code


Agent Relay sessions

  • claude session 8ab900cc · opened via gh pr create · last active 2026-10-31