Skip to content

squad init/upgrade wipes user-supplied env on the squad_state entry in .mcp.json #1893

Description

@tamirdresher

Summary

ensureSquadStateMcpInRoot in packages/squad-cli/src/cli/core/mcp-root.ts rebuilds the squad_state entry in the repo-root .mcp.json from scratch on every squad init and every squad upgrade, hardcoding env: {}. Any user-supplied environment variables on that entry are silently destroyed.

This bites users behind a corporate npm proxy hardest: the workaround for blocked public npm is to add npm_config_registry to the squad_state entry's env. That workaround is erased by the very next init or upgrade.

Reported in the Squad Teams channel by Jonathan Fischer, who described it as .mcp.json being "blown away" — accurate from his perspective, since squad_state was the only entry in his file.

Current behavior

mcp-root.ts lines 99-104:

const desired: McpServerEntry = {
  command: spec.command,
  args: [...spec.args],
  env: {},          // <-- unconditionally discards existing env
  tools: ['*'],
};

Two consequences:

  1. User env is destroyed. The entry is replaced wholesale, not merged.
  2. The idempotency guard can never fire for these users. The short-circuit at line 114 requires Object.keys(existing.env).length === 0, so a non-empty env guarantees a rewrite on every invocation — the file is rewritten even when nothing else changed.

Reproduce

  1. squad init in a fresh repo.

  2. Edit .mcp.json and add an env block to mcpServers.squad_state:

    "env": {
      "npm_config_registry": "https://internal-proxy.example/npm/"
    }
  3. Run squad upgrade (or squad init again).

  4. env is back to {}.

Expected behavior

User-supplied env keys on the squad_state entry should survive init and upgrade. Squad owns command and args (the version pin is deliberately managed), but env is user territory — there is no Squad-managed key in it today.

The equality check should also account for env so the no-write short-circuit works for users who have customized it.

Scope notes

  • init and upgrade share this code path exactly: init.ts:401, init.ts:424, and upgrade.ts:882 all call the same function. Switching from init to upgrade is not a workaround.
  • The rest of the file is not affected. Other mcpServers.* entries and top-level keys are preserved correctly through the JSON round-trip, and malformed files are refused rather than clobbered. The bug is confined to the squad_state entry.
  • Sibling writers in the same module already get this right: tombstoneStaleSquadStateInProjectMcp and ensureSquadStateMcpInUserConfig both bail out rather than overwrite user edits, and the user-config variant does not compare env at all.

Proposed fix

Merge existing.env into the desired entry instead of resetting it, and include env in the idempotency comparison so unchanged configs are left untouched.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions