fix(cli): relay init — portable MCP command, real Codex project trust, workspace key out of git

Merged
KH
16d ago
+905-117

Three bugs found running npx agent-relay@next init --yes --scope project (15.0.0-next.2) on a real machine.

1. MCP command pointed at npx's cache

Every tool config got command: ~/.npm/_npx/<hash>/node_modules/.bin/agent-relay. Cause: npx puts its cache .bin first on PATH, so the "agent-relay on PATH that is the running CLI" branch matched the temporary copy.

resolveMcpLaunch now takes the scope and never writes a cache path (PATH entries under _npx/bunx- are skipped):

Run fromProject scope (shared files)Global scope (this machine)
AGENT_RELAY_MCP_COMMAND setthe overridethe override
npm global / install.sh launcher on PATHagent-relay mcp (by name)absolute launcher / PATH path, same as before
npx, lasting agent-relay on PATHagent-relay mcpthat absolute path
npx, nothing installednpx -y agent-relay@<this version> mcpnpx -y agent-relay@<this version> mcp
install.sh launcher not on PATHnpx -y agent-relay@<version> mcpthe launcher path
source checkoutabsolute node + script, with a warning to set AGENT_RELAY_MCP_COMMAND before sharingabsolute node + script

Why: project files are shared with teammates, so they must not contain home paths. From npx we no longer fall back to an install.sh launcher that isn't on PATH, because on the test machine that was an old, unrelated 14.x binary; pinning the version that ran init is safer. Unknown/dev versions fall back to an unpinned agent-relay. init runs npx -y agent-relay@<v> --version once so the first MCP start doesn't hit a client's startup timeout. Entries init wrote earlier (AGENT_RELAY_MANAGED_BY=relay-init) are updated on re-run.

2. Codex project scope: how Codex 0.154 actually finds project config

Tested against codex-cli 0.154.0 with an isolated CODEX_HOME:

  • Codex does read <project>/.codex/config.toml, but only when that exact project root has [projects."<root>"] trust_level = "trusted" in $CODEX_HOME/config.toml. The root is the nearest .git (default project_root_markers), else the cwd. A subdirectory of a trusted git root works.
  • A trusted parent directory doesn't count. That's why the trial failed even though /home/khaliqgant is trusted.
  • -c 'projects."<dir>".trust_level="trusted"' is ignored for this (trust is read from the file).
  • codex exec never asks. Only interactive codex offers to trust on first run. codex.toml is not a thing.

So the old note ("it asks on first run") was only half true. Now:

  • codexProjectTrust reads the trust state. When the project is trusted, no note is printed. When it's unset, the note gives the exact [projects."<root>"] line to add and says parent trust and codex exec don't help. When it's untrusted, the note says so.
  • In the guided flow, relay init offers (default yes) to record trust the way Codex's own prompt does. --yes never trusts on your behalf, because trust also relaxes Codex's approval defaults.

I checked the other tools' project-scope claims with their own mcp list in the project. All are accurate: Claude shows "Pending approval" (the note says so), OpenCode connects, Grok and Devin list it, and Pi says "ignored because the project is not trusted" (the note says so).

Note: codex exec (approval policy never) refuses non-read-only MCP tools such as send_dm with "MCP tool call requires approval". Run it with -c 'mcp_servers.agent-relay.default_tools_approval_mode="approve"', the same way claude -p needs --allowedTools. init does not auto-approve Relay tools.

"Failed to connect to Agent Relay socket." This was not a hook. It was Codex's own final answer: with no relay MCP tools loaded (bug 2), the model fell back to the global messaging-agents-on-the-relay skill and curled the desktop socket, which Codex's sandbox blocks (the socket answers fine outside the sandbox). It goes away once the MCP tools load. Nothing to change in relay-desktop.

3. Workspace key showed in git status

ensureGitExclude in @agent-relay/config is now exported and worktree/submodule aware: it follows a .git file's gitdir: and commondir, matching git rev-parse --git-path info/exclude. It writes an anchored, escaped pattern (/.agentworkforce/relay/, /sub/.agentworkforce/relay/ for nested projects). It's called from the workspace-key writer (writeProjectWorkspaceKey* in @agent-relay/cloud), so every command that saves a project key is covered, not just init. Only the standard <project>/.agentworkforce/relay dir is excluded, and .gitignore is never touched (same approach as the broker's worktree_guard).

Evidence (real tools, this machine)

Fresh git init projects, existing local server on :8787:

  • Installed CLI (worktree build linked as agent-relay on PATH): configs say command = "agent-relay", with no rk_/at_, _npx or /home/ in any of the 6 configs. git status --porcelain shows no .agentworkforce/, and .git/info/exclude ends with /.agentworkforce/relay/. codex mcp list showed nothing before trust and agent-relay agent-relay mcp … enabled after. codex exec --skip-git-repo-check -c 'mcp_servers.agent-relay.default_tools_approval_mode="approve' ran send_dm and it completed. claude -p --mcp-config .mcp.json --strict-mcp-config --allowedTools "mcp__agent-relay__*" (prompt on stdin) returned "e2e-ping-7731 from codex" from codex-relay-init-e2e-1.
  • npx run (CLI under a …/_npx/<hash>/node_modules layout with its .bin first on PATH, version 15.0.0-next.2): all configs got npx -y agent-relay@15.0.0-next.2 mcp. A re-run over entries rewritten to the old _npx path reported updated and left 0 _npx hits. Round trip: Codex send_dm completed, and Claude read From codex-relay-init-e2e-2: "e2e-ping-4419 from codex via npx".
  • ~/.agentworkforce/relay/workspaces.json active stayed ghsub-live. The throwaway projects and the Codex trust entries added for them were removed afterwards.

Tests

  • agent-tools-mcp.test.ts: the npx .bin-first-on-PATH case at both scopes, the stale launcher not used, unpinned unknown version, by-name vs by-path per scope, checkout warning, and Codex trust (git root, parent doesn't count, quoted keys, untrusted, recording trust keeps the file and its 0600 mode and is idempotent, refuses a [projects] table).
  • init.test.ts: the trust note with --yes, no note when already trusted, scope passed to the resolver plus npx warm-up, re-run replaces an _npx entry, a real git init project keeps .agentworkforce out of git status without a .gitignore, and the guided flow trusting, declining, and skipping it at global scope.
  • project-namespace.test.ts / project-workspace-key.test.ts: anchored pattern, nested project, legacy entry recognised, linked worktree, no repo, and that the writer excludes only the standard dir.
  • npx tsc --noEmit -p packages/cli|config|cloud is clean. vitest run packages/cli passes 2456 tests; config + cloud + related suites pass 864.

Docs: docs/quickstart.md updated (Codex trust, portable command, git exclude). README untouched (#2008).

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Agent Relay sessions

  • claude session a188ad6b · opened via gh pr create · last active 2026-10-28
  • claude session 5a4f854b · contributor · ran gh pr commands · last active 2026-10-28