fix(cli): relay init — portable MCP command, real Codex project trust, workspace key out of git
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 from | Project scope (shared files) | Global scope (this machine) |
|---|---|---|
AGENT_RELAY_MCP_COMMAND set | the override | the override |
| npm global / install.sh launcher on PATH | agent-relay mcp (by name) | absolute launcher / PATH path, same as before |
npx, lasting agent-relay on PATH | agent-relay mcp | that absolute path |
| npx, nothing installed | npx -y agent-relay@<this version> mcp | npx -y agent-relay@<this version> mcp |
| install.sh launcher not on PATH | npx -y agent-relay@<version> mcp | the launcher path |
| source checkout | absolute node + script, with a warning to set AGENT_RELAY_MCP_COMMAND before sharing | absolute 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(defaultproject_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/khaliqgantis trusted. -c 'projects."<dir>".trust_level="trusted"'is ignored for this (trust is read from the file).codex execnever asks. Only interactivecodexoffers to trust on first run.codex.tomlis not a thing.
So the old note ("it asks on first run") was only half true. Now:
codexProjectTrustreads 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 andcodex execdon't help. When it's untrusted, the note says so.- In the guided flow,
relay initoffers (default yes) to record trust the way Codex's own prompt does.--yesnever 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-relayon PATH): configs saycommand = "agent-relay", with nork_/at_,_npxor/home/in any of the 6 configs.git status --porcelainshows no.agentworkforce/, and.git/info/excludeends with/.agentworkforce/relay/.codex mcp listshowed nothing before trust andagent-relay agent-relay mcp … enabledafter.codex exec --skip-git-repo-check -c 'mcp_servers.agent-relay.default_tools_approval_mode="approve'ransend_dmand 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_moduleslayout with its.binfirst on PATH, version 15.0.0-next.2): all configs gotnpx -y agent-relay@15.0.0-next.2 mcp. A re-run over entries rewritten to the old_npxpath reportedupdatedand left 0_npxhits. Round trip: Codexsend_dmcompleted, and Claude readFrom codex-relay-init-e2e-2: "e2e-ping-4419 from codex via npx". ~/.agentworkforce/relay/workspaces.jsonactivestayedghsub-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_npxentry, a realgit initproject keeps.agentworkforceout ofgit statuswithout 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|cloudis clean.vitest run packages/clipasses 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](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Agent Relay sessions