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 / 8b53302 — basic-ftp overridden to 5.3.1, clearing CVE-2026-39983 + 3 sibling HIGH advisories (plus a lockfile tripwire)
279fda4 — browse: 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
Summary
gstack-update-checkdecides "is there an upgrade?" by string-comparingmain:VERSIONagainst the localVERSIONfile, but/gstack-upgradeapplies the upgrade withgit reset --hard origin/main. Those are two different clocks:main.Between releases,
mainaccumulates commits whilemain:VERSIONstays 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 returnsmain's VERSION. The version it accurately returns just isn't a function ofmain's content.Reproduction (observed today, 2026-07-28)
Global git install at
~/.claude/skills/gstack, sitting on thev1.60.1.0release commit7c9df1c:Meanwhile:
main's tip isa325940(2026-07-14); the last VERSION bump is7c9df1c(2026-07-09). Somainhas been 31 commits ahead of its own VERSION for ~2 weeks, and no client can see it.Why
main:VERSIONis stale (and why this will recur)Commit
f155481bumped VERSION/CHANGELOG/package.json to1.60.2.0. Commitc7e182bthen reverted all three, with this rationale in its message: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:VERSIONlagsmainby design, so any release cadence slower than the merge cadence reopens the window.c7e182bis the trigger here, not the cause.Impact
The 31 unreachable commits include security fixes:
f55e270/8b53302—basic-ftpoverridden to 5.3.1, clearing CVE-2026-39983 + 3 sibling HIGH advisories (plus a lockfile tripwire)279fda4—browse: case-insensitive exfiltration-domain blocklist matching (a mixed-case host currently bypasses the blocklist)ea648b7—/careful: fail closed on compound recursive deletesa2a447a— consistent credential-action classificationA 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 fresh1.60.1.0while 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_SHAviagit ls-remote ... refs/heads/mainat line 197. Compare it to localgit rev-parse HEADand reportUPGRADE_AVAILABLEon mismatch. The VERSION comparison can stay as the display string. Vendored installs would need a recorded source SHA (e.g. written to.gstack-source-shaat setup).Option B — make releases the real unit of distribution. Have
/gstack-upgradecheck out the latest release tag rather thanorigin/main. Thenmain:VERSIONbecomes 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 whenmainis more than N commits past its last VERSION bump, so this window is visible to maintainers rather than to users who find out by readinggit log.The semver-order guard at line 225-229 is also worth a look under Option A — it suppresses
UPGRADE_AVAILABLEwheneverREMOTEdoesn't sort strictly higher thanLOCAL, so a legitimate VERSION revert onmainis indistinguishable from the stale-CDN regression it's meant to absorb.Environment
1.60.1.0, global git install (~/.claude/skills/gstack)