Skip to content

[SEVERE] upgrade --self --dry-run performs a real @latest install and can downgrade previews #2295

Closed

Description

@tamirdresher

Summary

Squad 2.0.0-preview.1 ignores --dry-run when it is combined with squad upgrade --self. The command proceeds into a real global install of @bradygaster/squad-cli@latest. On the tested preview installation, latest resolved to 1.0.0, so a successful run would both mutate the machine during a dry run and downgrade the preview CLI. Only an unrelated EPERM prevented the mutation.

The default self-upgrade target also ignores the installed channel. A preview/insider install should stay on that channel unless the user explicitly requests another one, and --insider should be honored.

Environment

  • Squad CLI 2.0.0-preview.1, installed from the exact published release tarball
  • Windows 11
  • Node.js 22
  • CFS-only npm host (https://packagefeedproxy.microsoft.io/npm/)

Repro

squad --version
npm view @bradygaster/squad-cli dist-tags --json
squad upgrade --self --dry-run
squad --version

Observed

The dry run entered the real install path:

npm install -g @bradygaster/squad-cli@latest
npm error code EPERM
npm error syscall unlink
EPERM: operation not permitted, unlink

Before and after, squad --version returned:

2.0.0-preview.1

The registry mirror exposed only:

latest: 1.0.0

The command exited non-zero only because the global installation could not be unlinked. Had the install succeeded, the preview CLI would have been replaced by 1.0.0.

Expected

  • --dry-run must make upgrade --self side-effect-free.
  • The output should describe the package/version/channel that would be installed.
  • A preview/insider installation should default to its installed channel rather than latest.
  • --insider must select the insider/preview channel consistently for self-upgrade.

Impact / Severity

SEVERE. This violates the dry-run safety contract and can silently downgrade or replace a global CLI installation. The observed machine was protected only by an unrelated filesystem error.

Proposed fix

  1. Check dryRun before any package-manager spawn in the self-upgrade path.
  2. Resolve the installed package version/channel and preserve preview/insider by default.
  3. Make --insider override channel resolution explicitly.
  4. Add injectable-spawn tests for npm/pnpm/yarn proving all --self --dry-run combinations execute zero mutations and print the resolved target.

Activity

  1. dat-intellis commented on Nov 8, 2026

    @dat-intellis

    Adding the backend-migration variant of this dry-run defect for stable 1.1.0, pinned to release commit c24da2aad2ab7576f6b68dec2bc50b2fe2dc7a8a:

    squad upgrade --state-backend orphan --dry-run

    This does not require --self. In the released upgrade dispatcher, dryRun is passed to runUpgrade(), which previews and returns. The dispatcher then calls migrateStateBackend(dest, upgradeStateBackend) unconditionally.

    The migration function has no dry-run option. It can create the state branch, import decisions/histories, remove their working-tree copies, rewrite configuration, install hooks, and update .gitignore, despite the command's preview-only promise.

    I checked #2303: its dispatcher change explicitly skips backend migration and hook installation during a dry run, so that merged fix covers this variant too. I am adding the stable-version evidence here rather than opening a duplicate or claiming a regression in the fix. Since 1.1.0 is still listed as the latest stable release, please include this behavior in stable release notes and identify the first stable version with the fix.

  2. tamirdresher commented on Nov 9, 2026

    @tamirdresher
    CollaboratorAuthor

    There is no fixed stable release to point to yet. I checked the release tags and package dist-tags: v1.1.0 is still latest and predates #2303. The fix is included in v2.0.0-preview.2 and v2.0.0-preview.3, both prereleases. It covers the backend-migration dry-run variant described here as well as --self. I have prepared local release-note wording for both variants to carry into the next stable release; those notes are not published, and no stable version or publication date is assigned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions