Repository navigation
fix(claude): portable launch commands and host-aware init next steps (#2462, #2453) - #2470
bradygaster wants to merge 7 commits into
Conversation
`squad init --client claude` ended with "Run copilot --agent squad" and a Copilot-only tip. initNextSteps builds the closing lines from the chosen hosts: Claude Code gets `claude --agent squad --mcp-config .mcp.json` (or `claude` in plugin mode) and a matching `claude -p` tip, other hosts get one line each, and no --client still means Copilot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…2462) With the CLI installed, init and upgrade wrote `node /abs/cli-entry.js state-mcp` into .mcp.json, the other hosts' MCP configs and every Claude hook, so other clones failed and the commit leaked local paths. Committed files now come from resolveCommittedSquadStateMcpSpec: - an installed CLI becomes `node .squad/bin/squad.mjs state-mcp`, a launcher generated from the plugin launcher's template that finds a CLI per machine (SQUAD_CLI, the repo's node_modules, squad on PATH; npx only with SQUAD_ALLOW_NPX=1) and skips older CLIs - a standalone bundle becomes `squad state-mcp`; pinned npx is kept - Claude hooks run the launcher through "$CLAUDE_PROJECT_DIR", and isSquadHookHandler recognises that exact form for refresh and rollback `squad doctor` gains a portable-launch check. The plugin launcher is regenerated from the shared template (same behaviour, still preview.4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🟠 Impact Analysis — PR #2470Risk tier: 🟠 HIGH 📊 Summary
🎯 Risk Factors
📦 Modules Affecteddocs (3 files)
root (5 files)
scripts (1 file)
squad-cli (7 files)
squad-sdk (2 files)
tests (7 files)
This report is generated automatically for every PR. See #733 for details. |
🏗️ Architectural Review
Automated architectural review — informational only. |
🛫 PR Readiness Check
PR Scope: 📦🔧 Mixed (product + infrastructure)
|
| Status | Check | Details |
|---|---|---|
| ❌ | Single commit | 7 commits — consider squashing before review |
| ✅ | Not in draft | Ready for review |
| ✅ | Branch up to date | Up to date with dev |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | Changeset file found |
| ✅ | Scope clean | No .squad/ or docs/proposals/ files |
| ✅ | No merge conflicts | No merge conflicts |
| ✅ | Copilot threads resolved | 1 active Copilot thread(s) resolved (4 outdated skipped) |
| ✅ | CI passing | All checks passing |
Files Changed (25 files, +1115 −337)
| File | +/− |
|---|---|
.changeset/init-next-steps.md |
+5 −0 |
.changeset/portable-launch.md |
+6 −0 |
CHANGELOG.md |
+9 −1 |
docs/src/content/docs/features/mcp.md |
+2 −0 |
docs/src/content/docs/scenarios/troubleshooting.md |
+10 −0 |
docs/src/content/docs/scenarios/upgrading.md |
+8 −7 |
packages/squad-cli/src/cli/commands/client.ts |
+4 −2 |
packages/squad-cli/src/cli/commands/doctor.ts |
+78 −0 |
packages/squad-cli/src/cli/core/clients.ts |
+2 −2 |
packages/squad-cli/src/cli/core/init.ts |
+55 −10 |
packages/squad-cli/src/cli/core/mcp-root.ts |
+2 −2 |
packages/squad-cli/src/cli/core/mcp-spec.ts |
+142 −2 |
packages/squad-cli/src/cli/core/upgrade.ts |
+10 −2 |
packages/squad-sdk/src/client/adapters/claude-hooks.ts |
+32 −7 |
packages/squad-sdk/src/client/adapters/claude-plugin.ts |
+109 −28 |
plugins/squad/scripts/squad.mjs |
+22 −5 |
scripts/docs-site-location.mjs |
+0 −5 |
test-fixtures/spec/suite-v0.2/upgrade-matrix.json |
+248 −247 |
test/__snapshots__/host-projection-regression-guards.test.ts.snap |
+1 −1 |
test/cli/upgrade-portability.test.ts |
+9 −5 |
test/docs-site-location.test.ts |
+0 −9 |
test/init-next-steps.test.ts |
+80 −0 |
test/mcp-spec-init.test.ts |
+2 −1 |
test/portable-launch.test.ts |
+277 −0 |
test/squad-20-upgrade-matrix.test.ts |
+2 −1 |
Total: +1115 −337
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
…ed (#2462) The upgrade matrix caught two gaps: - On a machine where the CLI runs only by path (no `squad` on PATH, no project install), the repository launcher found nothing and the squad_state probe failed right after upgrade. init/upgrade now record the CLI entry in `<Squad home>/cli.json` (outside the repository), and both launchers try it after PATH, with the same version floor. - `.squad/bin/squad.mjs` is a Squad-owned path a plain upgrade may create: add it to the matrix fixture's squadOwned list and the upgrading guide. The recorded runs are refolded in a follow-up commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Status: the upgrade matrix (ubuntu) caught two real gaps, fixed in f3ac9bd: the launcher now also finds the CLI that ran 🤖 Generated with Claude Code |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved launcher flaws permit executable writes outside the workspace through symlinks and can exclude the required script from commits, breaking fresh clones.
5 open issues
Validate launcher paths against workspace symlink escapes · New Unignore the repository launcher during installation · New Handle Windows absolute paths and PATH-based commands in doctor · New Base Claude next steps on the persisted plugin mode · New Detect transient npx execution before writing the repository launcher · New
What changed in this PR
This PR addresses two Claude Code first-run issues (#2462 and #2453): machine-specific launch commands in committed files and init instructions that recommend the wrong host.
Changes:
- Introduces a shared repository launcher and routes committed MCP configurations and Claude hooks through portable commands.
- Adds portability diagnostics to
squad doctorand host-aware next steps tosquad init. - Adds regression coverage, documentation, and release notes.
| File | Description |
|---|---|
test/portable-launch.test.ts |
Tests portable specs, launcher behavior, hooks, and diagnostics. |
test/mcp-spec-init.test.ts |
Accepts the committed-file resolver in init. |
test/init-next-steps.test.ts |
Tests host-specific instructions and plugin mode. |
test/cli/upgrade-portability.test.ts |
Checks portable upgrade output and launcher creation. |
test/__snapshots__/host-projection-regression-guards.test.ts.snap |
Updates the MCP configuration snapshot hash. |
plugins/squad/scripts/squad.mjs |
Regenerates the plugin launcher with shared project lookup. |
packages/squad-sdk/src/client/adapters/claude-plugin.ts |
Shares launcher generation between plugins and repositories. |
packages/squad-sdk/src/client/adapters/claude-hooks.ts |
Generates and recognizes repository-launcher hook commands. |
packages/squad-cli/src/cli/core/upgrade.ts |
Uses and describes portable launch specs during upgrade. |
packages/squad-cli/src/cli/core/mcp-spec.ts |
Resolves committed specs and writes repository launchers. |
packages/squad-cli/src/cli/core/mcp-root.ts |
Marks portable specs as non-machine-specific. |
packages/squad-cli/src/cli/core/init.ts |
Uses portable specs and prints host-aware next steps. |
packages/squad-cli/src/cli/core/clients.ts |
Uses portable specs for client synchronization. |
packages/squad-cli/src/cli/commands/doctor.ts |
Adds committed-launch portability diagnostics. |
packages/squad-cli/src/cli/commands/client.ts |
Resolves portable specs without writing during client listing. |
docs/src/content/docs/scenarios/troubleshooting.md |
Documents recovery from clone-related launch failures. |
docs/src/content/docs/features/mcp.md |
Explains launcher resolution and installation options. |
CHANGELOG.md |
Records portability and host-instruction fixes. |
.changeset/portable-launch.md |
Adds SDK and CLI patch release notes. |
.changeset/init-next-steps.md |
Adds CLI release notes for host-aware instructions. |
Review effort: Balanced (auto)
- Launcher writes go through resolveInsideRoot, so a symlinked squad directory or a dangling link at the launcher path can't redirect the write outside the workspace; Squad keeps the previous spec instead. - The launcher moves to `.squad/squad.mjs`: many repositories ignore `bin/`, which would leave `.squad/bin/squad.mjs` uncommitted. - A CLI running from a package runner's cache (npm `_npx`, pnpm `dlx`, yarn `dlx-`/`xfs-`) keeps the exact-version npx command and is not recorded in cli.json: that install is gone after the command exits. - doctor recognises Windows and POSIX absolute paths on every OS and only checks the command on disk when it is absolute (a bare `node` is found on PATH). - init's next steps use the clients and plugin mode .squad/config.json records when no flag is given, so re-running init stays accurate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The repository moved from bradygaster/squad to github/squad. The API no longer serves the 1.0.1 and 1.1.0 release assets under the old name (HTTP 404), so those rows were silently skipping; the asset ids and sha256s are unchanged under github/squad. Recorded runs are refolded from this head's matrix run in the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Linux and Windows re-recorded from the pull-request matrix run at 9b254ba (both 35 pass, 7 skipped, the same coverage as before), folded with render-upgrade-matrix.mjs's mergeRun, and the "Tested upgrade paths" section re-rendered. Only runs data and the generated docs change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Refolded the upgrade matrix ( 🤖 Generated with Claude Code |
The refolded upgrade matrix links to github/squad runs and releases, so its generated section keeps no bradygaster/squad links and the HISTORICAL_REPO_LINKS entry for upgrading.md was flagged as unused. Remove it and the test cases that used it as their example; whatsnew.md still covers section-scoped exemptions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>


Two first-run fixes from the Claude Code end-to-end test (epic #2438, checklist #2469).
Closes #2462
Closes #2453
#2462: committed files carry no machine paths
With the Squad CLI installed (global, project, or a source build),
squad initandsquad upgradewrotenode /abs/path/cli-entry.js state-mcpinto.mcp.json, the other hosts' MCP configs and every Claude hook in.claude/settings.json. Any other clone failed, and the commit leaked the local username and layout.Committed files now use a portable form, chosen in
resolveCommittedSquadStateMcpSpec(mcp-spec.ts), which every writer of committed files calls (init, upgrade,syncClients,squad client list, read-only):local-entry)node .squad/squad.mjs state-mcp: the repository launchersquad state-mcp(bundles putsquadon PATH and may have no Node)pinned)npx -y @bradygaster/squad-cli@<version> state-mcpsquadRepoLauncherinclaude-plugin.ts), so there's one implementation. It resolvesSQUAD_CLI, then the repository'snode_modules, thensquadon PATH, skipping a CLI older than the version that wrote it. It usesnpxonly withSQUAD_ALLOW_NPX=1. It finds the repository from its own location, not the working directory. A user-authored file at that path is never overwritten; Squad keeps the old spec instead.squad initandsquad upgraderecord the CLI entry in<Squad home>/cli.json(outside the repository). Both launchers try it after PATH, so a CLI that only runs by path (a source build, CI) works on the machine that set up the repository. Package-runner caches (_npx,dlx) are never recorded, and keep the exact-version npx command instead.resolveInsideRoot, so symlinks can't redirect the write outside the workspace. It lives at.squad/squad.mjs, not underbin/, which many repositories ignore..squad/squad.mjsis declared Squad-owned inupgrade-matrix.jsonand the upgrading guide. That changes the harness hash, so the recorded Linux and Windows runs are refolded from this PR's matrix run.node "$CLAUDE_PROJECT_DIR/.squad/squad.mjs" hook claude <Event>, the documented form for project hooks.isSquadHookHandlermatches that exact shape, so refresh and rollback still strip only Squad's handlers, andsquad upgradereplaces old machine-specific handlers.squad doctorgets a new portable squad_state launch check. It fails on a committed path that doesn't exist on this machine or a missing launcher, and warns on this machine's own absolute path.x-squad.machineSpecificis nowfalsefor the portable forms.scripts/squad.mjswas regenerated from the shared template. The only change is aprojectStart()helper, with the same behaviour. It's still pinned to2.0.0-preview.4.#2453: init names the host you chose
squad init --client claudeended with "Runcopilot --agent squad" and a Copilot-only tip.initNextStepsnow builds the closing lines from the chosen hosts:claude --agent squad --mcp-config .mcp.json, orclaudein plugin mode, plus a matchingclaude -ptip.--client: Copilot, as before.Testing
test/portable-launch.test.ts(15 tests) covers the spec mapping, launcher writes (created, unchanged, updated, a user file skipped), hook form, merge and strip, the look-alike guard, and the launcher run for real (project install, older CLI skipped,SQUAD_CLI, hooks a no-op until opted in). It also covers the doctor check.test/init-next-steps.test.ts(5 tests).test/cli/upgrade-portability.test.tsnow asserts the portable form and that no machine path is present;mcp-spec-initsource check; the Copilot snapshot in host-projection guard 1 (.mcp.jsonhash only: the launch command is the intended change).squad init --client claudefrom a source build in a scratch repo writes no/Users/path anywhere. The hooks come out in$CLAUDE_PROJECT_DIRform and one runs from another directory. The launcher skips the Homebrewpreview.3on PATH and prints install instructions.npm testlocally: 13,432 passed, 32 failed. The failures are the known macOS ones (export-import, upstream-, memory-, profile-fs-guard), files that fail only under full-suite load (they pass alone: hostile-integration, squad-observer, gh-aw), and three that fail the same way on a cleanorigin/devworktree (thesquad-20monorepo-subfolder and satellite-repo bridge scenarios, andupgrade-profile-teamroot).npm run lintclean; docs markdownlint and cspell clean for the changed files.Docs:
features/mcp.mdexplains the launcher;scenarios/troubleshooting.mdcovers "squad_state or Claude hooks fail after cloning". There are changesets and[Unreleased]CHANGELOG entries; no version bump.🤖 Generated with Claude Code