Skip to content

fix(orchestrators): register Chorus at Claude Code user scope#35

Closed
chrisayl wants to merge 1 commit into
chorus-codes:mainfrom
chrisayl:fix/claude-mcp-user-scope
Closed

fix(orchestrators): register Chorus at Claude Code user scope#35
chrisayl wants to merge 1 commit into
chorus-codes:mainfrom
chrisayl:fix/claude-mcp-user-scope

Conversation

@chrisayl
Copy link
Copy Markdown
Contributor

Summary

chorus init registers Chorus to Claude Code under projects.<homedir>.mcpServers.chorus in ~/.claude.json, so the MCP server only surfaces when Claude Code is launched from the user's home directory — not from the project they actually ran chorus init in. From any real project it silently does nothing.

Root cause: registerClaudeMcpServer defaults the project key to os.homedir(), and runOrchestratorAutoConnect (in init.ts) never passes a projectDir. The cwd is never read, so the entry always lands at projects.<HOME>.

Fix

Shell out to claude mcp add chorus --scope user --env CHORUS_DAEMON_URL=… -- node <binPath> mcp, matching the codex and gemini orchestrators (which already register at user / global scope via their respective CLIs). The entry lands at top-level mcpServers in ~/.claude.json and is available from every project — which is the user expectation reflected in the README ("wires up MCP with every CLI / IDE it detects").

Behaviour:

  • Fresh install: one claude mcp add … --scope user call.
  • Already registered with the same binPath: no-op ({ added: false }).
  • Stale entry with a different binPath: claude mcp remove … --scope user then re-add.
  • Legacy projects.<dir>.mcpServers.chorus entries from older releases are ignored — left in place; the new user-scope entry takes precedence in Claude Code.

Test plan

  • pnpm test — 756/756 pass (+6 new in tests/claude-orchestrator.test.ts covering fresh install / idempotency / stale-entry remove-then-add / legacy-project-scope ignored / claude mcp add failure path).
  • pnpm typecheck — clean.
  • pnpm lint — 45 warnings, same as main (no new ones).
  • Manual: chorus init from a non-home directory and confirm claude mcp list shows chorus (run from any project dir).

🤖 Generated with Claude Code

`chorus init` was writing the Chorus MCP entry to
`projects.<homedir>.mcpServers.chorus` in `~/.claude.json`, so it only
surfaced when Claude Code was launched from the user's home directory
— not from the project they actually ran `chorus init` in.

Shell out to `claude mcp add ... --scope user` instead, matching the
codex/gemini orchestrators. Entry lands at top-level `mcpServers` and
is available from every project. Removes any stale entry first.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chorus-codes added a commit that referenced this pull request May 14, 2026
…persedes #35, #36, #37) (#39)

* fix(orchestrators): register Chorus at Claude Code user scope

`chorus init` was writing the Chorus MCP entry to
`projects.<homedir>.mcpServers.chorus` in `~/.claude.json`, so it only
surfaced when Claude Code was launched from the user's home directory
— not from the project they actually ran `chorus init` in.

Shell out to `claude mcp add ... --scope user` instead, matching the
codex/gemini orchestrators. Entry lands at top-level `mcpServers` and
is available from every project. Removes any stale entry first.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(mcp): forward repoPath from create_chat to the daemon

`create_chat` drops `repoPath` on the floor — the schema doesn't
accept it and the daemonFetch body doesn't forward it. The daemon's
`/chats` route already supports `repoPath`, but with nothing
forwarded `prompt-builder.ts:45` falls back to the daemon's own
`process.cwd()`, which is locked to packageRoot by `start.ts:324`.

Two concrete effects:

1. Relative paths in `files: [...]` resolve against the chorus npm
   install dir, miss, and get silently skipped — so the inline-packed
   file contents the reviewers + doer see in their prompt are wrong
   (usually empty).

2. The DOER (only) gets cwd = scratch dir instead of the user's
   repo (`doer-driver.ts:170`: `repoPath ?? doerDir`), so it can't
   read project files via its own tools and can't make the real
   edits the ship phase would commit.

Reviewers are NOT affected by this fix — they intentionally run in
a per-round scratch dir regardless of repoPath (`reviewer.ts:84`,
spelled out in `doer-driver.ts:165-168`).

Fix: add `repoPath: z.string().optional()` to `CreateChatSchema`,
default it to `process.cwd()` in `createChat`, forward in the POST
body. MCP servers spawned by Claude Code / Codex / Gemini inherit
the host's cwd (= the project), so the default lands at the right
path automatically. Explicit callers can still override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(windows): patch remaining spawn EINVAL call sites + audit fixes

- Add shell: win32 to daemon/routes/system.ts (opencode models probe)
- Add shell: win32 to cli/commands/update.ts (npm self-update spawn)
- Relax SAFE_WIN_PATH regex: whitelist -> blacklist for Unicode paths
- Add shell: isWindows to discoverNpmPrefixes (npm.cmd on Windows)
- Remove shell: win32 from ship.ts (git/gh are native .exe, no EINVAL risk)

Findings from multi-LLM code review (Gemini, DeepSeek, OpenCode).

* fix(windows): normalize CRLF line endings in persona frontmatter parser

Persona .md files checked out on Windows have CRLF line endings,
but the frontmatter parser checks for '---\n' which fails with
'---\r\n', throwing 'missing YAML frontmatter'. Adding .replace()
normalizes to LF before parsing.

Also adds pr-description.md documenting the full spawn EINVAL fix
with tri-review V3 results.

* fix: resolve spawn EINVAL error for Windows CLIs

* fixup: drop stray pr-description.md + tighten SAFE_WIN_PATH (add ^ to blacklist)

Adds tests for cmd.exe escape-char rejection and @-scoped npm package
path acceptance. Without ^ in the blacklist, a path containing `^"& cmd`
could break out of the shell-quoted wrap in buildVersionSpawn.

* fixup(review): apply chorus self-review fixes

Three convergent blocking findings from 8-reviewer panel:

1. claude.ts execFileAsync missing shell:win32 (caught by gemini-cli — the
   irony: this is the same EINVAL bug #37 fixes everywhere else).
2. SAFE_WIN_PATH missing ! (delayed expansion under setlocal
   enabledelayedexpansion). 6/8 reviewers flagged.
3. process.cwd() throwing ENOENT crashes createChat. 5/8 reviewers
   flagged — wrap in safeCwd() with homedir fallback.

Plus: friendlier error message in claude.ts pointing at minimum Claude
Code version when 'mcp add --scope user' is unsupported.

---------

Co-authored-by: Chris Aylott <chris.aylott@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: João Pedro Magalhães <pedropeixotomagalhaes@gmail.com>
Co-authored-by: chorus-codes <280607145+chorus-codes@users.noreply.github.com>
@chorus-codes
Copy link
Copy Markdown
Owner

Thanks for this — bundled into #39 with full credit (Co-authored-by) and squash-merged into main. We'll publish as v0.8.36 shortly.

The bundle picked up four convergent findings from a chorus self-review on the combined diff:

Closing this PR — merged into main via #39.

chorus-codes added a commit that referenced this pull request May 14, 2026
Bundles #39 (supersedes #35 chrisayl, #36 chrisayl, #37 magalz) — claude
orchestrator user-scope MCP registration, repoPath plumbing through MCP
create_chat, and Windows spawn EINVAL fix across 8 call sites. Plus
chorus-self-review fixups: ! and ^ added to SAFE_WIN_PATH blacklist,
shell:win32 on claude.ts execFileAsync, safeCwd() ENOENT fallback.

Co-authored-by: chorus-codes <280607145+chorus-codes@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants