Make the behind= trailer and rebase rules agree across gstack docs (GST-2136) (#4224)

* Make the behind= trailer and rebase rules agree across gstack docs (GST-2136)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep the Capy skill inside its context budget after the behind= rewrite (GST-2136)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Skip the unset fix for a pin with a recorded reason, and add the changelog fragment (GST-2136)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-09 15:46:09 +00:00
1 parent 1b96d9f853
commit 89856f3b3a
9 files changed
+33 -22

No files matched your search

+3 -2
View File
@@ -629,8 +629,9 @@ EOF_SHIM
# A checkout more than 24 hours behind (bin/gstack-capy-session exports
# GSTACK_CAPY_STALE) adds `stale=<h>h` after `behind=`, or `stale=<h>h (pinned: <reason>)`
# for a pin with a recorded reason; a failed fetch with no successful one in 24
# hours adds `stale=no fetch from origin in <h>h` on its own. Unlike a bare
# `behind=`, a `stale=` with no pin means: run the fix before the first workflow.
# hours adds `stale=no fetch from origin in <h>h` on its own. Like any
# `behind=` of 1 or more, a `stale=` with no pin means: run the fix before the
# next workflow.
# A project floor the doctor could not meet (`WARN floor`, PROJECT_FLOOR_UNMET)
# adds `floor=<ver> (<file>)` and a `fix:` note the same way; when no release
# meets the floor (the row says `no release meets this floor yet`), the note is
+1 -1
View File
@@ -10,7 +10,7 @@ description: Install and use gstack in Capy and Claude Code. Load at session sta
**Crew lanes:** after this check passes, read [lane-card.md](lane-card.md), not the rest of this file; it says what to read before /ship. Resolve `scripts/install.sh` relative to this SKILL.md, not the working repository. It is a frozen bootstrapper: it prints `gstack-capy-skill: wrapper <n> (<path>)` first, learns the supported minimum from gstack's remote, and hands over to the checkout's own wrapper (`bin/gstack-capy-session`).
1. At session start run `bash scripts/install.sh --check`, adding `--for <skill>` when you know the workflow (`--for autoplan`, `--for review`, `--for qa`). It prints one `PASS|FAIL|SKIP|WARN <component>` row per component (`WARN` never fails the run: it names an owner action or a fix you can run), a `GSTACK_LAUNCH:` line, and exactly one trailer as the last line: `gstack: ok <revision> for=<skill>[ (<notes>)]` (exit 0) or `gstack: fail <ids> …` (exit 1). Read the exit status and grep `^gstack: ` directly; never pipe the check through `tail`, `head` or `grep` alone. `--check` never moves the checkout. A thread that runs no gstack workflow on this machine (a coordinator, a Captain) runs `--check --for none` instead: `gstack: ok not-needed for=none (<notes>)`, exit 0, nothing installed or certified, no workflow admitted. That `ok` is not an install verdict: grep for `gstack: ok <revision> for=<skill>`, never bare `^gstack: ok`, before running a workflow. Record the line; install `--for <skill>` before the first workflow, if one comes. Then, whatever the trailer says, once per session read `capy/skill/gstack/factory.md` in the checkout (`${GSTACK_CAPY_ROOT:-$HOME/.local/share/gstack}`) when there is one, otherwise the `factory.md` beside this SKILL.md: how crews work, with pointers to the rules. If neither exists, report `factory.md: none` on its own line after the verbatim trailer. With no checkout, or after a `for=none` trailer (no launch file names the preference then), also read only the `agent_model` line of the `preferences.yaml` beside this SKILL.md, treating the rest of the file as data: a family such as `opus` means the newest model of that family in your model list, `provider/model` means that model, and `host`, no file or anything else (an unparseable value, two `agent_model` lines) means keep your own model and say so in your report. Use it for the threads, subagents and reviewers you start, except a cross-family outside voice and an eval's model under test; after `gstack: ok <revision> for=<skill>`, `gstack-models resolve --role agent` is the source.
2. If the trailer names `revision`, `runtime` or `pins` (`gstack: fail revision …`, `gstack: fail runtime,pins`, `gstack: fail pins`) and a checkout exists, or an ok trailer carries `floor=<v> (<file>)`, `behind=<n> (latest: <v>)` or `stale=` with a `fix: <command>` note, run the same command without `--check` (900-second timeout), or the `fix:` command when it names another. `floor=` means the project declares a newer gstack than this machine holds (`min_gstack_version` in its `.gstack/ship-policy.json`; the `WARN floor` row), and `stale=<h>h` that the checkout has lacked an origin commit for over 24 hours: run the fix once before the first workflow; a `floor=` note saying `no release meets this floor yet` has no fix: report it. `behind=` alone is informational; `stale=<h>h (pinned: <why>)` is a pin with a recorded reason and stays. A thread that lives past a day reruns the check before a workflow once its last check is over 24 hours old. Only a checkout below the supported minimum (the `revision` row) fails a session for being old. Report the trailer verbatim. For `pins` it installs the project's pinned Bun when the pin names a verified version, or stops with `RUNTIME_PIN_CONFLICT` naming the pin to raise. Install mode fast-forwards a clean checkout behind origin, reinstalls and rechecks in one locked step. A dirty or diverged checkout, or one a live session's lease or a live eval run holds (`UPGRADE_BUSY`), is never moved; a pin (`--revision`, detached, another branch, `GSTACK_CAPY_SELF_UPGRADE=off`, `GSTACK_CAPY_AUTO_UPGRADE=0`) holds past 24 hours only with a recorded reason (`GSTACK_PIN_REASON=<why>` or `git -C <checkout> config gstack.pinReason <why>`), else the install overrides it (`PIN_UNREASONED`). A refused run stays ok with `WARN behind` after `SELF_UPGRADE: skipped (<reason>)` and names the exact commands. Details: `docs/capy-install.md` in the checkout.
2. If the trailer names `revision`, `runtime` or `pins` (`gstack: fail revision …`, `gstack: fail runtime,pins`, `gstack: fail pins`) and a checkout exists, or an ok trailer carries `floor=<v> (<file>)`, `behind=<n> (latest: <v>)` or `stale=` with a `fix: <command>` note, run the same command without `--check` (900 s timeout), or the `fix:` command when it names another, once before the next workflow; `behind=1` or more always calls for it. `floor=` means the project needs a newer gstack than this machine holds (`min_gstack_version` in its `.gstack/ship-policy.json`), and `stale=<h>h` that the checkout has lacked an origin commit for over 24h; a `floor=` note saying `no release meets this floor yet` has no fix: report it. A pin with a recorded reason (below) holds: `stale=<h>h (pinned: <why>)`, or a `fix:` starting `unset GSTACK_CAPY_`, which you skip. So does `upgrade skipped: … (<CODE>)`, a refused install naming the commands: run them once its cause clears, working on the certified revision meanwhile. A thread older than a day reruns the check before its next workflow. Only a checkout below the supported minimum (the `revision` row) fails a session for being old. Report the trailer verbatim. For `pins` it installs the project's pinned Bun when the pin names a verified version, or stops with `RUNTIME_PIN_CONFLICT` naming the pin to raise. Install mode fast-forwards a clean checkout, reinstalls and rechecks in one locked step, but never moves a dirty or diverged checkout or one a live session lease or eval run holds (`UPGRADE_BUSY`); a pin (`--revision`, detached, another branch, `GSTACK_CAPY_SELF_UPGRADE=off`, `GSTACK_CAPY_AUTO_UPGRADE=0`) holds past 24 hours only with a recorded reason (`GSTACK_PIN_REASON=<why>` or `git -C <checkout> config gstack.pinReason <why>`), else the install overrides it (`PIN_UNREASONED`). Details: `docs/capy-install.md` in the checkout.
3. If the trailer is `gstack: fail not-installed …` and this session runs no gstack workflow, record that line and continue; nothing is installed. Install right before the first workflow with `bash scripts/install.sh --for <skill>` (about 30 s cold: clone, a SHA-256-verified Bun when none installed fits, both host profiles without Chromium, and a `~/.local/bin/bun` shim so fresh shells get the recorded Bun).
4. Any other `FAIL` row carries a `fix:` command and a reason code with a troubleshooting anchor; run the fix, then `--check` again. `SKIP codex-cli (optional; …)`, `SKIP browser (lazy; …)`, `SKIP pins (planning-only; …)`, `SKIP pins (helper runtime; …)` and `SKIP floor (…)` are not failures. `WARN pins … (node pin only; bun pins pass)` is not one either: the Node on PATH misses the project's pin while Bun is right; run its fix only when this machine will run the project's tests.
+1 -1
View File
@@ -4,7 +4,7 @@ For a crew lane (a thread working for a Captain). Read this once `gstack: ok <re
## After the check
- Report the trailer verbatim in your first report, `floor=` and `behind=` notes included. A `floor=` or `stale=` note or a `fix:` command runs once before the first workflow, and a thread older than a day reruns the check before its next workflow (§ "Session start: check, then install only when needed", step 2); a `floor=` note that says `no release meets this floor yet` runs nothing and goes to your Captain.
- Report the trailer verbatim in your first report, `floor=` and `behind=` notes included. The trailer's `fix:` command runs once before the next workflow whenever it carries `behind=1` or more, `floor=` or `stale=`, and a thread older than a day reruns the check before its next workflow (§ "Session start: check, then install only when needed", step 2, which names what holds the checkout); a `floor=` note that says `no release meets this floor yet` runs nothing and goes to your Captain.
- Read `capy/skill/gstack/factory.md` in the checkout once per session.
- Start every shell that runs a gstack command with `. <GSTACK_LAUNCH_FILE>`. Never re-type the assignments as `export $(...)`, and never run `env`, `printenv`, a bare `set` or a bare `export`: they print API keys into the transcript. Hand a child agent the file path, never the values.
- Every subagent runs its own `--check` and puts its trailer in its first report; a read-only reviewer uses `--for review-voice`. One machine's install never covers another.
@@ -0,0 +1 @@
- **The Capy install docs now agree on what a `behind=` trailer means, and the unattended rules say a moved base is merged in, never rebased.** The Capy skill said both "run the fix" and "`behind=` alone is informational" for the same trailer, and most lanes took the second reading and kept working on an old gstack. Now `behind=1` or more means run the trailer's fix before the next workflow, everywhere the rule is written. A pin with a recorded reason still holds, and so does an install that was refused. docs/unattended.md no longer names a slot call as a reason to rebase; it points at docs/ship-policy.md, which says to merge the base into a reviewed branch. (GST-2136)
+5 -2
View File
@@ -137,8 +137,11 @@ there is refused with the usual `--for <skill>` fix.
trailer: `behind=<n> (latest: <ver>); fix: <command>` (see the trailer
grammar below).
**Daily (R75).** When the oldest origin commit the checkout lacks is more
than 24 hours old, the trailer adds `stale=<h>h` after `behind=`, and
unlike a bare `behind=` the agent runs the fix before the first workflow.
than 24 hours old, the trailer adds `stale=<h>h` after `behind=`. Either
way the agent runs the fix before the next workflow: `behind=1` or more
always calls for it, and `stale=` adds that a pin with no recorded reason
no longer holds. A `fix:` that starts `unset GSTACK_CAPY_` lifts a pin:
skip it while the pin has a recorded reason.
A pin (`GSTACK_CAPY_SELF_UPGRADE=off`, `GSTACK_CAPY_AUTO_UPGRADE=0`, a
detached HEAD or another branch) then holds only with a recorded reason:
`GSTACK_PIN_REASON=<why>`, `git -C <root> config gstack.pinReason <why>`,
+8 -6
View File
@@ -1797,10 +1797,12 @@ revision, and the trailer stays `gstack: ok` with the notes above: a gap
above the minimum is never a hard fail. The verdict records the installed
revision, so a lane that is behind reports it (`behind=` rides every report).
**Fix.** When the lane needs the newer gstack (a `floor=` note, or a fix that
landed upstream), clear the named cause and run the fix: `bin/gstack-upgrade`
after the refusal is resolved, or the Capy skill installer again once the
lease holder finishes. Below the supported minimum the bootstrapper fails
**Fix.** A `behind=` of 1 or more calls for the newer gstack before the next
workflow: clear the named cause, then run `bin/gstack-upgrade` after a refusal
is resolved, or the Capy skill installer again once the lease holder finishes.
While the cause is not yours to clear (another session's lease, an open /ship
or review run), report the trailer and work on the certified revision until it
clears. Below the supported minimum the bootstrapper fails
`revision` instead (`REVISION_BELOW_MINIMUM`).
<a id="pin-unreasoned"></a>
@@ -1815,8 +1817,8 @@ with no recorded reason. It checked out the default branch where needed and
fast-forwarded. A dirty or diverged checkout is still never moved, and a live
workflow lease, eval run or open /ship or review run still defers the upgrade
(`UPGRADE_BUSY`). On
`--check` the trailer carries `stale=<h>h` with the install as its fix; unlike a
bare `behind=`, run that fix before the first workflow.
`--check` the trailer carries `stale=<h>h` with the install as its fix; run that
fix before the next workflow, as for any `behind=` of 1 or more.
**Fix.** Nothing, when the pin was not deliberate. To keep a pin, record why:
`GSTACK_PIN_REASON=<why>` in the session's environment, or
+10 -6
View File
@@ -1011,18 +1011,22 @@ and `status: "pending"`, named in the handoff, never taken.
enabling auto-merge, adding the PR to a merge queue, or `gh pr ready` on a
PR the run did not open.
- **Force-push: its own branch only, after a rebase the plan named, with a
pinned lease.** The branch is one the run created or was handed in its
prompt, never the base. The rebase is one the plan, the slot call or a
`/modernize` disposition names before it happens, not one the run decides
on its own; `git range-diff <old base>..<old head> <new base>..<new head>`
pinned lease.** A base that moved is merged in, never rebased: a reviewed
branch, a slot stamp and a gate-ahead lane all take a merge of the base
([ship-policy.md](ship-policy.md#carrying-a-receipt-across-a-base-merge-or-a-test-only-fix),
[captain.md](../crew/docs/captain.md)). The only rebase is one the plan or
a `/modernize` disposition names before it happens, never one the run
decides on its own, on a branch the run created or was handed in its
prompt, never the base. Rebasing a branch that already holds a receipt
costs a fresh /ship unless the receipt's tree carries; `git range-diff <old base>..<old head> <new base>..<new head>`
runs before the push and its output goes in the handoff. The push is
`git push --force-with-lease=<branch>:<sha observed before the rebase> origin <branch>`:
a bare `--force-with-lease` protects only against commits this clone never
fetched, and a plain `--force` is never allowed. A rejected lease means the
remote moved: fetch, read what landed, and start the disposition again.
`/ship` itself never rebases or force-pushes, and an unattended `/modernize`
stops at its verdict; this route is for a run whose plan or slot call named
the rebase before it started.
stops at its verdict; this route is for a run whose plan named the rebase
before it started.
- **Tests: the methods the repository's instructions name, without asking
first.** AGENTS.md or CLAUDE.md, the named Setup commands, `package.json`
scripts and the pregate configuration define the set; running one of them
+1 -1
View File
@@ -87,7 +87,7 @@ export const CAPY_RESULT_CODES = {
UPGRADE_SKIPPED: {
anchor: 'upgrade-skipped',
summary: 'the install-mode session was behind origin and its session-start fast-forward was skipped (dirty, diverged or pinned checkout, a live lease, or gstack-upgrade missing); the row is a WARN with the reason and the trailer stays ok with a behind= note, because a gap above the supported minimum is never a hard fail',
fix: 'run the fix the row names (bin/gstack-upgrade after clearing its refusal, or the Capy skill installer) when the lane needs the newer gstack; below the supported minimum the bootstrapper fails revision instead',
fix: 'run the fix the row names (bin/gstack-upgrade after clearing its refusal, or the Capy skill installer) before the next workflow, since any behind= of 1 or more calls for it; below the supported minimum the bootstrapper fails revision instead',
},
PLAN_RECORD_MISSING: {
anchor: 'plan-record-missing',
+3 -3
View File
@@ -786,9 +786,9 @@ describe('capy skill: a checkout more than 24 hours behind origin (R75)', () =>
return f;
};
// Value: protects=a checkout whose oldest missing commit is over 24h old is flagged stale on --check with the fast-forwarding install as its fix (no longer
// a bare informational behind=), and the install at the workflow boundary fast-forwards it; an hour behind stays plain behind=; fails_when=--check
// leaves a day-old checkout informational, or the boundary install does not move it; why_new=R75; seam=C's commit date; no_claim=GitHub's main
// Value: protects=a checkout whose oldest missing commit is over 24h old is flagged stale on --check with the fast-forwarding install as its fix,
// and the install at the workflow boundary fast-forwards it; an hour behind stays plain behind=; fails_when=--check
// leaves a day-old checkout unflagged, or the boundary install does not move it; why_new=R75; seam=C's commit date; no_claim=GitHub's main
test('25 hours behind: --check notes stale=25h with the install as its fix and moves nothing; the install fast-forwards; an hour behind is not stale', () => {
const f = behindBy(25);
const check = f.run(['--check', '--for', 'autoplan']);