Skip to content

update-check gates on main:VERSION but /gstack-upgrade installs main HEAD — installs sit silently 31 commits behind between releases (incl. security fixes) #2378

Description

@blackbullet01

Summary

gstack-update-check decides "is there an upgrade?" by string-comparing main:VERSION against the local VERSION file, but /gstack-upgrade applies the upgrade with git reset --hard origin/main. Those are two different clocks:

  • The check reads a release marker that contributors are explicitly told not to touch.
  • The upgrade installs all of main.

Between releases, main accumulates commits while main:VERSION stays pinned at the last released value. Every installed client on that release is told "up to date" and has no supported path to those commits — including security fixes.

This is not the stale-CDN problem already handled by the SHA-pinned raw URL at bin/gstack-update-check:183-202. That fix works correctly here: the SHA-pinned fetch accurately returns main's VERSION. The version it accurately returns just isn't a function of main's content.

Reproduction (observed today, 2026-07-28)

Global git install at ~/.claude/skills/gstack, sitting on the v1.60.1.0 release commit 7c9df1c:

$ ~/.claude/skills/gstack/bin/gstack-update-check --force
(no output — i.e. UP_TO_DATE)

Meanwhile:

$ git fetch origin && git rev-list --count HEAD..origin/main
31

$ git rev-parse --short HEAD origin/main
7c9df1c
a325940

$ git show origin/main:VERSION
1.60.1.0        # identical to local → line 220 returns UP_TO_DATE

main's tip is a325940 (2026-07-14); the last VERSION bump is 7c9df1c (2026-07-09). So main has been 31 commits ahead of its own VERSION for ~2 weeks, and no client can see it.

Why main:VERSION is stale (and why this will recur)

Commit f155481 bumped VERSION/CHANGELOG/package.json to 1.60.2.0. Commit c7e182b then reverted all three, with this rationale in its message:

Revert VERSION, CHANGELOG, and package.json bumps — leave release bookkeeping to maintainers / /ship, matching community PR practice.

That commit is correct. Community PRs shouldn't self-assign version numbers. Which is exactly why this isn't a one-off: the project's own contribution norm guarantees main:VERSION lags main by design, so any release cadence slower than the merge cadence reopens the window. c7e182b is the trigger here, not the cause.

Impact

The 31 unreachable commits include security fixes:

  • f55e270 / 8b53302basic-ftp overridden to 5.3.1, clearing CVE-2026-39983 + 3 sibling HIGH advisories (plus a lockfile tripwire)
  • 279fda4browse: case-insensitive exfiltration-domain blocklist matching (a mixed-case host currently bypasses the blocklist)
  • ea648b7/careful: fail closed on compound recursive deletes
  • a2a447a — consistent credential-action classification

A user who runs /gstack-upgrade, is told they're current, and reasonably concludes they have the blocklist fix does not have it. The failure is silent and reads as success — the same shape as #1974, but from version-gate granularity rather than a crash.

To actually get these I had to bypass the tool entirely (git stash && git reset --hard origin/main && ./setup). Note the version number does not change afterward, so the install is now materially different from a fresh 1.60.1.0 while reporting the same string — two installs claiming one version.

Suggested fixes

Pick one clock and use it for both the check and the upgrade:

Option A — compare commits (matches what the upgrade actually does). For git installs this is nearly free: the script already resolves _REMOTE_SHA via git ls-remote ... refs/heads/main at line 197. Compare it to local git rev-parse HEAD and report UPGRADE_AVAILABLE on mismatch. The VERSION comparison can stay as the display string. Vendored installs would need a recorded source SHA (e.g. written to .gstack-source-sha at setup).

Option B — make releases the real unit of distribution. Have /gstack-upgrade check out the latest release tag rather than origin/main. Then main:VERSION becomes an honest answer, at the cost of shipping fixes only at release boundaries.

Either resolves the mismatch. A cheap interim mitigation, independent of the above: have /ship (or CI) fail when main is more than N commits past its last VERSION bump, so this window is visible to maintainers rather than to users who find out by reading git log.

The semver-order guard at line 225-229 is also worth a look under Option A — it suppresses UPGRADE_AVAILABLE whenever REMOTE doesn't sort strictly higher than LOCAL, so a legitimate VERSION revert on main is indistinguishable from the stale-CDN regression it's meant to absorb.

Environment

  • gstack 1.60.1.0, global git install (~/.claude/skills/gstack)
  • macOS 15.5 (Darwin 25.5.0), Claude Code

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