garrytan / garrytan/gstack

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

Open
#2,378 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
133k
Forks
19.9k
Avg merge
18h 46m
Merged PRs (30d)
26

Description

## 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

Contributor guide

Open the contributing guide

Research direction

Start with bin/gstack-update-check, especially the SHA fetch around line 183, remote resolution at line 197, version gate at line 220, and semver guard at lines 225-229; then trace the /gstack-upgrade path. Reproduce the release-commit versus origin/main case and confirm the chosen approach makes the check and upgrade report the same state, including security-fix commits.

Written by the indexing model from the issue text.

Assessment

Tech stack
git
Domain
cli, devops, release
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.