Skip to content

fix(cli): follow externalized state in squad copilot and squad rc#1464

Merged
tamirdresher merged 1 commit into
bradygaster:devfrom
omercangumus:omercangumus/1397-1398-external-state-reads
Jul 14, 2026
Merged

fix(cli): follow externalized state in squad copilot and squad rc#1464
tamirdresher merged 1 commit into
bradygaster:devfrom
omercangumus:omercangumus/1397-1398-external-state-reads

Conversation

@omercangumus

Copy link
Copy Markdown
Contributor

What

squad copilot and squad rc now follow the stateLocation: external marker: roster reads and writes go to the external state dir instead of the local .squad/team.md.

Why

Closes #1397
Closes #1398
Part of #1402

After squad externalize, both commands kept operating on the working-tree copy. squad copilot edited (or, on add, effectively scaffolded) a local roster the externalized project never reads — the real team.md in the external dir never learned about @copilot. squad rc loaded its agent list from the same dead file, so the remote UI showed an empty or stale roster.

How

  • commands/copilot.ts: resolves through effectiveSquadDir() — the same external-state-aware resolution already used by export, build, loop, plugin, watch, and doctor (fix(cli): resolve externalized state in squad export #1443 precedent) — and reads/writes team.md in the effective state dir. The .github/copilot-instructions.md copy is a working-tree file and is untouched.
  • commands/rc.ts: the roster load moves into an exported loadRosterAgents(squadDir) that routes the team.md read through resolveStateDir(). Deliberately narrow: the squadDir passed to RemoteBridge stays the local path, because the bridge derives the copilot session cwd from it by stripping the /.squad suffix (packages/squad-sdk/src/remote/bridge.ts) — handing it the external state dir would point ACP sessions at the wrong directory. Only the read is redirected.
  • Legacy .ai-team behavior is unchanged in both commands — no external marker there, resolution falls through to the local path.
  • [External State] squad cast does not follow externalized state for agent discovery #1399 (squad cast agent discovery) is not in this PR; it crosses into squad-sdk's resolveSquadPaths/LocalAgentSource and lands separately.

Testing

  • test/cli/copilot.test.ts: two new tests against a marker-only .squad/ + external state dir (the layout squad externalize leaves behind, same APPDATA/XDG override pattern as the fix(cli): resolve externalized state in squad export #1443 export tests): add writes the Coding Agent section to the external team.md and does not grow a local one; --off removes it from the external file.
  • test/cli/rc.test.ts: two new tests for loadRosterAgents — local read without the marker, and the marker redirecting to the external team.md while a stale local roster is present (the stale copy must lose).
  • npm run build — passes. npm run lint (tsc) — passes. npm run lint:eslint — 0 errors (pre-existing warnings only).
  • npx vitest run test/cli/copilot.test.ts test/cli/rc.test.ts test/cli/export-import.test.ts — all four new tests pass; the export suite (adjacent feature area) passes.
  • Known local-env caveat: the first module exports … test in each of these two files times out at 5s on this Windows box — verified byte-identical failures on a clean upstream/dev checkout with a clean build (stash + rebuild + rerun), so it's the documented pre-existing cold-import flakiness (same class as noted on feat(cli): add squad update-check --json for tooling/CI #1440), not this change. All other 51 tests in the two files pass.
  • rc.ts is committed with CRLF endings; edits preserve CRLF per CONTRIBUTING's line-ending rule, so git diff --check reports CR on added lines — expected for this file, no mixed endings introduced (git ls-files --eol: i/crlf w/crlf).

⚠️ Quick Check

  • Changeset added: .changeset/fix-1397-1398-external-state-reads.md (patch bump, @bradygaster/squad-cli only)

PR Readiness Checklist

Branch & Commit

  • Branch created from dev
  • Branch is up to date with dev
  • Verified diff contains only intended changes (git diff --cached --stat + git diff -w whitespace pass)
  • PR is not in draft mode
  • Commit history is clean (single commit)

Build & Test

  • npm run build passes
  • npm test passes (see testing notes for the pre-existing local cold-import timeouts)
  • npm run lint passes (type check clean)
  • npm run lint:eslint passes (0 errors)

Changeset

  • Changeset added (patch, @bradygaster/squad-cli)

Docs

  • N/A — behavior fix inside existing commands, no new user-facing surface

Exports

  • N/A — loadRosterAgents is a named export from commands/rc, which already has a subpath export; no new module

Breaking Changes

None. Projects without the external marker resolve to the local .squad/ exactly as before.

Waivers

None.

After squad externalize, both commands kept operating on the local
.squad/team.md. squad copilot edited (or scaffolded) a stale local
roster the externalized project never reads, and squad rc loaded its
agent list from the same dead file, so the remote UI showed an empty
or stale team.

squad copilot now resolves the roster through effectiveSquadDir() —
the same resolution used by export, build, loop, plugin, watch, and
doctor — and reads/writes team.md in the effective state dir. squad rc
routes the team.md read through resolveStateDir(), extracted into an
exported loadRosterAgents() so it's testable. The squadDir passed to
RemoteBridge stays the local path on purpose: the bridge derives the
copilot session cwd from it by stripping the /.squad suffix, so handing
it the external state dir would point sessions at the wrong directory.

Fixes bradygaster#1397
Fixes bradygaster#1398
Part of bradygaster#1402

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit d6b5159

PR Scope: 📦🔧 Mixed (product + infrastructure)

⚠️ 2 item(s) to address before review

Status Check Details
Single commit 1 commit — clean history
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 No Copilot review threads
CI passing 7 check(s) still running

Files Changed (5 files, +167 −18)

File +/−
.changeset/fix-1397-1398-external-state-reads.md +5 −0
packages/squad-cli/src/cli/commands/copilot.ts +9 −9
packages/squad-cli/src/cli/commands/rc.ts +21 −8
test/cli/copilot.test.ts +63 −1
test/cli/rc.test.ts +69 −0

Total: +167 −18


This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

@github-actions

Copy link
Copy Markdown
Contributor

🟡 Impact Analysis — PR #1464

Risk tier: 🟡 MEDIUM

📊 Summary

Metric Count
Files changed 5
Files added 1
Files modified 4
Files deleted 0
Modules touched 3

🎯 Risk Factors

  • 5 files changed (≤5 → LOW)
  • 3 modules touched (2-4 → MEDIUM)

📦 Modules Affected

root (1 file)
  • .changeset/fix-1397-1398-external-state-reads.md
squad-cli (2 files)
  • packages/squad-cli/src/cli/commands/copilot.ts
  • packages/squad-cli/src/cli/commands/rc.ts
tests (2 files)
  • test/cli/copilot.test.ts
  • test/cli/rc.test.ts

This report is generated automatically for every PR. See #733 for details.

@omercangumus

Copy link
Copy Markdown
Contributor Author

green and unreviewed for a couple of days now — this is the copilot/rc half of the external-state cluster, same fix pattern as #1465 which already merged

@tamirdresher
tamirdresher merged commit e57b904 into bradygaster:dev Jul 14, 2026
14 checks passed
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.

[External State] squad rc probes and reads local state directly [External State] squad copilot reads and writes local state only

2 participants