Skip to content

Workspace-only squad_state MCP not loaded in copilot -p mode → agents forge audit-log entries #1247

Closed

Description

@tamirdresher

Summary

When Squad is bootstrapped (e.g., via squad init or installed in a new repo), it writes the squad_state MCP server entry to workspace .mcp.json. In non-interactive copilot -p ... invocations — which is how every copilot -p-driven test, CI run, and many automated workflows operate — that workspace MCP is not loaded into the agent's tool list. The agent then silently falls back to writing directly to .squad/decisions/*, .squad/memory/audit.jsonl, etc. via platform Create/Edit/bash tools, and forges audit-log entries that mimic the SDK provider's JSON format.

Reproduction

Tested on Windows 10 / Node 24.16 / @bradygaster/squad-cli v0.10.0-build.7 with both #1245 (memory_* MCP bridge) and #1246 (squad.agent.md drop-box → memory_write) patches applied locally.

  1. Fresh repo, run squad init (or simulate by writing only workspace .mcp.json).
  2. copilot mcp list → shows squad_state under "Workspace servers" ✅
  3. copilot mcp get squad_state → Tools: * (all), Source: Workspace (.../.mcp.json) ✅
  4. Run a directive prompt non-interactively:
    copilot -p "Important directive: never force-push on shared branches. Capture this for the team." --agent squad --allow-all-tools --no-color
  5. Observed: 0 MCP tool calls. Agent's own summary: "Memory tools weren't available, so used local-file fallback (STATE_BACKEND=local)."
  6. Agent wrote a fake audit entry like:
    {"class":"POLICY","ts":"2026-06-09T17:21:18Z","summary":"...","fallback":"local-file (memory_write tool unavailable)","actor":"copilot-coordinator","op":"write"}
    — note ts vs the real provider's timestamp, op vs action, and missing id/path/reason/provider.

Workaround that fixes the symptom

Add squad_state to user-level ~/.copilot/mcp-config.json instead of (or in addition to) workspace:

copilot mcp add squad_state -- node "<absolute path to squad-cli>/dist/cli-entry.js" state-mcp

Re-running the same prompt then produces 4 MCP calls in a clean session: squad_state_health → memory_classify → memory_write → squad_state_write. Real audit entry with id, path, reason, provider. Real memory/index.json entry. Real memory/policy-inbox/*.md file. 100% reliable across 3 reproduction runs.

Why this matters

  1. Silent data fabrication. The agent writes JSON to audit.jsonl that looks like a real memory.write entry but isn't. Any tooling that parses this log can't tell real from forged. This violates the basic governance contract that the audit log is the source of truth for what was written.
  2. Bridge + spawn-template fixes are wasted. PR feat(mcp): bridge governed memory tools (memory.*) through squad_state MCP server #1245 and Issue arch: directive capture + sub-agent decision-recording bypass the governed memory classifier #1246 are exactly the right work, but neither matters when the agent doesn't actually see the MCP tools at runtime.
  3. No telemetry. There's no warning when the agent falls back. A user looking at the run output sees a confident-looking "📌 Captured." message and no indication that the governance pipeline was bypassed.

Suggested fixes

In order of decreasing scope:

  1. Bootstrap registers at user level. squad init should run copilot mcp add squad_state ... (or equivalent for VS Code) instead of writing .mcp.json, since that path works reliably in non-interactive mode. Or — better — do both.
  2. Refuse to forge. squad.agent.md should explicitly forbid writing to .squad/memory/audit.jsonl via shell, and forbid writing to .squad/decisions/inbox/* via Create/Edit when squad_state_write is unreachable. Fail loud: tell the user "MCP not loaded, run squad mcp install to fix".
  3. Health-check warning at greeting. Coordinator already runs squad_state_health as PRE-CHECK step 0 — if that throws because the MCP isn't loaded, surface the error to the user instead of silently degrading.
  4. Investigate workspace-MCP loading in -p mode. Workspace MCPs may require interactive approval that never happens in -p mode. If that's the cause, copilot CLI behavior could be reported upstream.

Related

Cross-reference: see PR #1245 comment and Issue #1246 comment for the full A/B test runs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions