/ship always reports the version queue offline: Step 12 sends uppercase BUMP_LEVEL to a lowercase-only util, with stderr suppressed
- Dominant language
- TypeScript
- Stars
- 133k
- Forks
- 19.9k
- Avg merge
- 18h 46m
- Merged PRs (30d)
- 26
Description
## Summary
`/ship` Step 12 passes an **uppercase** bump level to `bin/gstack-next-version`, which
accepts only lowercase. The util exits 2 with a clear message, the skill suppresses
stderr, and the `||` fallback maps every failure to `{"offline":true}`.
An agent following the skill literally therefore gets "offline" on **every ship**, always
falls back to local arithmetic, and is told the queue is unreachable rather than that a
bad argument was sent. Workspace-aware ship — sibling detection, claimed-version
listing, collision warnings — never runs.
gstack 1.78.0.0.
## The mismatch
`ship/SKILL.md:709-711` defines the value set in uppercase:
```
- **MICRO**: <50 lines, trivial tweaks/config. **PATCH**: 50+ lines, no feature signals.
- **MINOR**: **ASK** if any feature signal ... **MAJOR**: **ASK** ...
Save as `BUMP_LEVEL`.
```
`ship/SKILL.md:715` (template: `ship/SKILL.md.tmpl:195`) passes it through verbatim:
```bash
QUEUE_JSON=$(bun run .../gstack-next-version --base --bump "$BUMP_LEVEL" --current-version "$BASE_VERSION" 2>/dev/null || echo '{"offline":true}')
```
`bin/gstack-next-version:449-452` rejects it:
```js
if (!["major", "minor", "patch", "micro"].includes(bump)) {
console.error(`Error: --bump must be major|minor|patch|micro (got ${bump})`);
process.exit(2);
}
```
## Repro
```bash
$ bun run ~/.claude/skills/gstack/bin/gstack-next-version --base master --bump MICRO --current-version 0.1.0.0
Error: --bump must be major|minor|patch|micro (got MICRO)
$ echo $?
2
$ bun run ~/.claude/skills/gstack/bin/gstack-next-version --base master --bump micro --current-version 0.1.0.0
{"version":"0.1.0.1","claimed":[],"siblings":[],"reason":"no collision; clean bump from base",...}
```
## Why it matters
`ship/SKILL.md:718` turns the swallowed exit into a **false diagnosis**:
> If `offline`/util fails: fall back to local `BUMP_LEVEL` arithmetic and print
> `⚠ workspace-aware ship offline — using local bump only`
The user is told the network/queue is unavailable. Nothing is offline; the call was
malformed. Nobody investigates a message that names an external cause.
The feature this disables exists to prevent a documented real incident —
`bin/gstack-next-version:470-479`:
> On 2026-08-12 ... `gh pr list` failed during a ship, this util reported offline, the
> bump fell back to local arithmetic, and 0.1.57.0 was allocated to a second PR while an
> open one already claimed it — both merged ... Auditing that repo's history found FOUR
> such pairs going back three weeks.
That is the same failure mode this bug reproduces on every run, not just when `gh` is
down. And `lib/version-source.ts:22-24` explains why git can't catch the result: "Two
branches cut from the same base then pick the same version, and git merges that without a
conflict because both sides set one line to identical text."
Observed cost in one session: the fallback produced a version that would have moved
`VERSION` backward when a sibling branch landed. The correct answer from the util was a
different number.
This is the shape of #2018 in your own changelog — "features returning empty results with
a green checkmark."
## Note on test coverage
The golden fixtures carry the same invocation (`test/fixtures/golden/claude-ship-SKILL.md:715`,
`codex-ship-SKILL.md:1929`, `factory-ship-SKILL.md:2364`), so the broken call is baked into
expected output and no test can catch it.
## Suggested fix
Three, ideally all:
1. **Normalize at the call site** in `ship/SKILL.md.tmpl:195` —
`--bump "$(printf '%s' "$BUMP_LEVEL" | tr 'A-Z' 'a-z')"` — or lowercase the documented
value set in Step 2. One or the other; today the doc and the util disagree.
2. **Accept case-insensitively** in `bin/gstack-next-version` (`bump.toLowerCase()` before
the `includes` check). Cheap and makes the CLI forgiving of both spellings.
3. **Stop mapping every failure to `offline`.** Drop `2>/dev/null`, or branch on the exit
code: exit 2 is a usage error the agent should surface and fix, not a queue outage.
Reporting a malformed call as "offline" is what kept this invisible.
Contributor guide
Research direction
Start with ship/SKILL.md.tmpl:195 and bin/gstack-next-version:449-452, then reproduce the uppercase and lowercase commands from the issue. Check the generated ship/SKILL.md and golden fixtures for the invocation, and verify that valid uppercase input reaches workspace-aware version handling while malformed arguments are not reported as offline.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- shell, typescript
- Domain
- cli, release, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100