# Installing gstack on a Capy cloud machine Three layers, each owned by gstack: | Layer | Where | Job | |---|---|---| | Bootstrapper | `capy/skill/gstack/scripts/install.sh` (copied once into the owner's Drive skill folder; frozen) | clone when absent; fetch origin's default branch and read the supported minimum from `FETCH_HEAD:capy/support.env`; in install mode fast-forward a clean, unpinned checkout below it; exec the full wrapper | | Full wrapper | `bin/gstack-capy-session` (moves with the checkout) | map the session start onto the installer: `--check` certifies (and reports a checkout behind origin), otherwise fast-forward a checkout behind origin (`bin/gstack-upgrade --ff-only-no-reinstall`), then install or repair | | Installer | `bin/gstack-capy-install` | Bun, both host profiles, the doctor, the install verdict, the launch line, the trailer | The vendored skill (`capy/skill/gstack/SKILL.md`) tells a Capy agent how to use them. The Drive folder is a mirror: the owner copies `capy/skill/gstack/` there when asked; nothing writes the Drive automatically. Direct use, without the Drive skill (a subagent or reviewer machine): clone into the root the bootstrapper resolves when nothing is there, then run the checkout's own bootstrapper by absolute path, from the project directory, so the floor check and fast-forward still run and the verdict is keyed by the caller's project. The same command serves every later session (an existing checkout is fast-forwarded, never re-cloned): ```bash test -f "$HOME/.local/share/gstack/setup" || git clone --quiet --single-branch --depth 1 https://github.com/garrytan/gstack.git "$HOME/.local/share/gstack" && GSTACK_CAPY_ROOT="$HOME/.local/share/gstack" bash "$HOME/.local/share/gstack/capy/skill/gstack/scripts/install.sh" --for autoplan ``` The text comes from `capyBootstrapCommand` in `lib/doctor-components.ts` (Bash: `gstack_capy_bootstrap_cmd`); the not-installed fix lines print it with the root they checked. ## Reviewer machines: `--for review-voice` A dispatched reviewer (an /autoplan voice, a second opinion) reads a plan and reports; it runs no workflow. `--for review-voice` gives its machine a real trailer in seconds: clone or fast-forward, Bun at gstack's own supported minimum (the project's pins are not applied, since no repo tests run there; the trailer says `pins: skipped (helper runtime)`), no `./setup`, no host profiles and no bun shim. The doctor certifies only the read-only helpers (`PASS helpers`: `gstack-pregate sealed`, `gstack-plan-reality`, `gstack-artifact` and the `gstack-autoplan` reads such as `status` and `contract`). Its verdict covers no host profile, so a workflow started there is refused (`INSTALL: fail verdict — … (review-voice certifies only the read-only helpers); fix: run GSTACK_CAPY_ROOT= bash /capy/skill/gstack/scripts/install.sh --for …`). A review-voice check never replaces an ok verdict that a fuller install wrote for the same HEAD and project. Every /autoplan reviewer prompt ends with this command (`GSTACK TRAILER`); the warm run's budget, under 20 s, is measured by `test/capy-review-voice.test.ts`. ## Session start 1. `bash /scripts/install.sh --check --for ` (under a second when a checkout exists). It fetches refs only and never moves the checkout. With no checkout it prints `gstack: fail not-installed — ...` and the session records that line and continues when it runs no gstack workflow; install right before the first workflow. 2. When the trailer is `gstack: fail revision ...` or `gstack: fail runtime ...` and a checkout exists, run the same command without `--check`. Below the supported minimum, a clean checkout on the remote default branch is fast-forwarded (`gstack-capy-skill: upgraded -> `), reinstalled and rechecked as one transaction under `.gstack-capy-install.lock` (the bootstrapper holds it and hands it over with `GSTACK_CAPY_LOCK_HELD=1`). A pinned (`--revision`, detached HEAD, another branch), dirty or diverged checkout is never moved; the trailer names the cause and the exact commands ([REVISION_BELOW_MINIMUM](troubleshooting.md#revision-below-minimum)). `GSTACK_CAPY_AUTO_UPGRADE=0` disables the fast-forward. 3. The minimum is the floor, not the trigger. Above it, a checkout behind origin's default branch still moves at install-mode session start: the full wrapper runs `bin/gstack-upgrade --ff-only-no-reinstall` (every refusal of `gstack-upgrade` applies), prints `SELF_UPGRADE: done -> ` and re-execs the new checkout's wrapper, which installs from the new revision (`GSTACK_CAPY_UPGRADED_FROM` set, so setup is never skipped). A refusal prints `SELF_UPGRADE: skipped ()`, and the session is never certified ok: the trailer is `gstack: fail behind — installed is commits behind origin (latest ) and the session-start upgrade was skipped: ; fix: (UPGRADE_BUSY|UPGRADE_REFUSED)`, exit 1. `SELF_UPGRADE: none (current)` when nothing is ahead; `GSTACK_CAPY_SELF_UPGRADE=off` (or `GSTACK_CAPY_AUTO_UPGRADE=0`) prints `SELF_UPGRADE: disabled ()`. `--check` never moves the checkout; behind origin it prints `gstack-capy-session: behind origin/: -> ( commits); fix: bash /scripts/install.sh [--for ] (fast-forwards)` and the agent runs that fix. Whenever the session ends behind origin (`--check`, or a self-upgrade that was skipped or disabled), the ok trailer also says so, because lanes relay only the trailer: `behind= (latest: ); fix: ` (see the trailer grammar below). 4. When the fetch fails (offline), the bootstrapper says `remote minimum unknown (fetch failed)` and checks the local install only; it never claims the minimum, and nothing is fast-forwarded. The upgrade target is always the tip of the default branch: every commit there is a stamped release that passed CI. Recovery runs forward, never back: an install interrupted after a fast-forward leaves the trailer `fail`, and the next install or `bin/gstack-upgrade` run completes the pending install stages (setup is never skipped after a fast-forward) or refuses and names the cause. Checking out an older commit is not a supported rollback; it does not undo setup migrations that already ran. Between minimums, `bin/gstack-upgrade --check` says `current ` or `behind -> ( commits)`, and `bin/gstack-upgrade` fast-forwards and reinstalls (`bin/gstack-capy-install` on a Capy install, else `./setup --refresh-registered`). It refuses a dirty, diverged or pinned checkout with [UPGRADE_REFUSED](troubleshooting.md#upgrade-refused) and never stashes or resets. Several agents sharing one checkout (eight Capy subagents on a machine) hold workflow leases on it: every `bin/gstack-skill-start` touches `/gstack-leases/` (`bin/gstack-lease touch`; pruned after 120 minutes). Each lease records its process's start identity (Linux: boot id and `/proc` start time; elsewhere `ps -o lstart`), because a pid is reused after a reboot or a machine wake. `bin/gstack-upgrade`, and so the session-start self-upgrade, refuses with [UPGRADE_BUSY](troubleshooting.md#upgrade-busy) while a lease held by a live process outside its own chain exists, naming each holder as `pid

(m )`. The team-mode SessionStart hook (`bin/gstack-session-update`) honors the same leases: it logs `SKIP leased pid=

/m` to `analytics/session-update.log` and tries again at the next session start, without writing its hourly throttle stamp. A lease whose pid now belongs to another process, or an identity-less lease from an older gstack past 120 minutes, is stale: `bin/gstack-lease live` reclaims it (`gstack-lease: reclaimed stale lease …`). Stale needs proof: two well-formed identities from the same backend that differ (`ps` is read under `LC_ALL=C TZ=UTC0`). An identity that cannot be read, a malformed record or a backend change keeps the lease. `touch` (publish by rename, prune) and reclaim (reread, then delete only the record judged stale) run under one lock in the lease directory, a `.lock` directory whose `owner` file names its holder's pid and start identity, so a renewal is never deleted and a live lease never disappears from a concurrent scan. The lock is a directory because `mkdir` is atomic on every host, while Git Bash on Windows cannot `ln -s` to a target that does not exist (#3330). A taker publishes a claim before its `mkdir` and renames it in as the owner after, so an ownerless lock is broken only once no claimant lives and the lock is at least 2 s old and unchanged while checked, which keeps a taker between its two steps safe. A claim that records no start identity is abandoned after 2 minutes, so a reused pid cannot keep an orphan forever; an owner that names no valid pid counts as a dead holder, and an owner file that cannot be read keeps the lock. A live holder that never lets go bounds the wait at 300 tries or 30 s. A lock that names a holder is broken only when that holder is dead or its pid was reused, never by age, and breakers serialize on a second lock and reread the lock before removing it. A symlink `.lock` left by an older gstack is honored the same way. Three known limits remain. An older gstack's `ln -s` onto a directory lock links inside it and succeeds, so a process that started before an upgrade of this checkout can share the lock with a newer one. And a breaker killed while holding the second lock, followed by two live breakers clearing it at once, can let a reclaim delete a renewal. On a host without start identities, a taker stalled over 2 minutes between its `mkdir` and its owner rename can be broken and then publish into its successor's lock. While a live process holds the lock, or it cannot be taken at all, `touch` exits 3 without writing (`gstack-skill-start` reports that the session holds no lease) and a reclaim keeps the lease. `bin/gstack-lease list` shows `alive|dead|stale`, and `GSTACK_UPGRADE_FORCE=1` overrides. ## What the installer does 1. Reuses the checkout it lives in (or clones into `--root

`), under the lock. It never resets or pulls; `--revision ` pins a fresh clone, and an existing checkout at another revision is reused with the trailer note `requested: (run /bin/gstack-upgrade to move)`. A fresh clone prints `cloned at ()` (the bootstrapper's clone too, through `GSTACK_CAPY_CLONED`). 2. Preflight: `git`, `curl`, `node`, `jq`, then Bun. The project's pins (`--project `, default the git toplevel of the current directory) are read with the doctor's own parser (`bin/gstack-runtime-pins.ts`: engines, packageManager, `.tool-versions`, `.bun-version` and CI `bun-version:` lanes for this platform). CI exact pins for this platform are alternatives: a repository may test several Bun versions (gstack's own CI pins 1.4.2 and runs one job on 1.4.4), so a Bun equal to any one of them satisfies CI; CI ranges and every non-CI pin stay required. One resolver picks the Bun (`gstack_bun_resolve` in `bin/gstack-bun-version.sh`; `resolveBun` in `lib/pregate/runtime.ts` is its TypeScript twin), and the installer, `--check`, the doctor, the bun shim and install admission all use it. Candidates, first eligible wins: the store copy of the project's exact pin (rule `pin`), the store copy of the install default (`default`), `$BUN_INSTALL/bin/bun`, `~/.bun/bin/bun` (`home`), then every `bun` on PATH. Eligible means it runs, reports a version at or above the supported minimum (1.4.2) and satisfies the project's pins. When nothing qualifies, the installer downloads one exact release archive, verifies its SHA-256 against `bin/gstack-bun-version.sh` and unpacks it into the versioned store `/runtimes/bun//bun`, under `~/.gstack-runtime.lock`. Each version directory also holds `bunx -> bun`. A broken entry is repaired without a window (its files are renamed over their predecessors), and `~/.bun/bin` is never written. The archive is the project's non-CI exact pin when gstack carries a verified archive for it (`GSTACK_BUN_VERIFIED`: 1.4.2, 1.4.3, 1.4.4); with only CI exact pins, the install default when it is one of them, else the highest verified one; else the install default `GSTACK_BUN_DEFAULT` (1.4.4, above the 1.4.2 minimum; one CI job runs the quick free tier on it). `GSTACK_BUN_INSTALL_VERSION=` overrides the default; a version without a verified archive or below the minimum stops the install ([BUN_INSTALL_VERSION_INVALID](troubleshooting.md#bun-install-version-invalid)). With a non-CI exact pin, only that version satisfies it. Any other exact pin (below the minimum, two different non-CI ones, or one without a verified archive), or a range gstack's minimum does not satisfy, stops with both constraints ([RUNTIME_PIN_CONFLICT](troubleshooting.md#capy-install-runtime-conflict), exit 3) before `./setup`. With no Bun at or above the security floor, the default is installed first so the pins can be read. Node follows the same rule (`bin/gstack-node-runtime.sh`): `engines.node`, `.nvmrc`, `.tool-versions` and CI `node-version:` lanes, non-CI exact pins required and CI exact pins in different jobs alternatives. The first node that satisfies them wins: the node first on PATH (rule `PATH`), then for each wanted version gstack's store copy `/runtimes/node//bin/node` (`store`) and nvm's `${NVM_DIR:-~/.nvm}/versions/node/v/bin/node` (`nvm`), then every other `node` on PATH. When nothing qualifies and the project names an exact version, install mode downloads `node-v-.tar.gz` and `SHASUMS256.txt` from `https://nodejs.org/dist/v/` (`GSTACK_NODE_DIST_URL` overrides the base), checks the archive's SHA-256 against that list and publishes the tree in the store by rename. A CI-only pin set installs its highest version. `/usr/local/bin` and nvm are never written. Two different non-CI exact Node pins stop with [RUNTIME_PIN_CONFLICT](troubleshooting.md#capy-install-runtime-conflict). `--check` keeps the recorded Node while it still reports its version and satisfies the pins, and never installs one; the doctor's `pins` row reads whatever node the launch PATH gives, and a Node mismatch names the node it read and the installer command that fixes it. 3. `./setup --host claude --no-prefix`, then `./setup --host codex --model gpt-6-astra`, both with `--no-browser`. Skipped when this exact HEAD was set up with the same hosts, model and browser flag (recorded in the git dir after a successful setup) and `gstack-doctor --check` passes; a warm rerun takes about a second. Each setup ends with its own `gstack ready ()`; the installer then prints `gstack ready (claude, codex)`. 4. `gstack-doctor --check` for the requested workflow (`--for `, `--only `, or `--require `; default `claude,codex,patch`), run with the launch PATH, so the trailer certifies the Bun workflows run. 5. The install verdict (schema 2), written atomically to `/install-verdicts//.json`: checkout, project, HEAD, the workflow and required components it certified, whether pins were skipped as planning-only, the pin it honoured, the resolved Bun (`bun_path`, `bun_version`, `bun_rule`, and `bun_install` when this run downloaded it), the resolved Node when the project pins one (`node_path`, `node_version`, `node_rule`), the trailer, the remote minimum, the launch PATH and the time. Admission refuses a workflow whose running Bun is not the one the verdict recorded ([BUN_RESOLUTION_DRIFT](troubleshooting.md#bun-resolution-drift)), and so do the pregate's lanes and guards, which also refuse an invalid verdict ([INSTALL_UNVERIFIED](troubleshooting.md#install-unverified)) under `GSTACK_CAPY=1` or whenever a verdict exists. `--check` keeps the recorded Bun while it exists, reports the recorded version and satisfies the pins; a candidate that would now resolve first is a `note:` line, and only a recorded Bun that is gone, changed or no longer satisfies the pins fails the doctor's `runtime` row. A schema 1 verdict (keyed by checkout only) is still read. 6. On a Capy machine (`CAPY_MACHINE_CONFIG` set or `~/.capy/system` present), install mode writes the bun shims described below. `--check` runs only steps 4 and 5 (no clone, Bun install, setup or shim). ## The bun shim A fresh shell on Capy finds the image's `/usr/local/bin/bun` (1.3.14) first, so a `bun test` typed without the `GSTACK_LAUNCH` prefix used to run the wrong Bun. Install mode therefore writes `~/.local/bin/bun` and `~/.local/bin/bunx` (`~/.local/bin` precedes `/usr/local/bin` on the Capy image's PATH). Each is a small POSIX script whose second line is the marker `# gstack-bun-shim: gstack-owned; delete this file to remove it`. At every call the `bun` shim finds the git toplevel around the working directory and runs, in order: the Bun that project's install verdict recorded (the `none` verdict outside any project and inside the gstack checkout), the store copy of the project's exact pin, the store copy of the install default, then `$BUN_INSTALL/bin/bun`, `~/.bun/bin/bun` and every non-shim `bun` on PATH, first at or above the supported minimum, then at or above the security floor. A pin is used only when it is `x.y.z` and its store entry is a real binary inside the store (no `..`, symlink escape or shim-marked file). A project pinning a Bun the store lacks prints `gstack bun shim: pins bun , which is not in gstack's runtime store; run ... --check here`; an unrecorded, unpinned repo falls through quietly. `bunx` runs `bun x` through the `bun` shim beside it. - `GSTACK_BUN_SHIM_EXPLAIN=1 bun --version` prints `gstack bun shim: project= record= chose= rule=` (and names a missing record). - `GSTACK_BUN_SHIM=0` at install time writes no shim. - Removing it: delete `~/.local/bin/bun` and `~/.local/bin/bunx`. gstack replaces only a missing file or a file carrying the marker; a foreign file or a dangling link there is left untouched with a note, and sourcing the launch file (`. `) stays the way to reach the certified Bun. - The doctor's `runtime` row notes the shim; the resolver never treats a shim-marked file as a Bun candidate. ## The one line to grep The last line is the trailer: ``` gstack: ok 1.91.96.0 for=autoplan gstack: ok 1.91.96.0 for=review (requested: v1.91.90.0 (run /bin/gstack-upgrade to move); latest: 1.91.101.0 available) gstack: ok 1.91.96.0 for=autoplan (pins: skipped (planning-only)) gstack: ok 1.91.101.0 for=autoplan (behind=12 (latest: 1.91.107.0); fix: bash /scripts/install.sh --for autoplan) gstack: fail runtime,pins ``` An ok trailer carries at most one parenthesised note group, `; `-separated, in this order: `requested: …` (an existing checkout at another revision than `--revision`), `pins: skipped (planning-only|no project|helper runtime)`, then either `behind= (latest: )` followed by `fix: ` (the session ended commits behind origin's default branch after a fetch that succeeded in this run; `bin/gstack-capy-session` exports `GSTACK_CAPY_BEHIND`, `_BEHIND_TO` and `_BEHIND_FIX`, so this works with every bootstrapper revision), or `latest: available` (origin's default branch has a newer VERSION; the bootstrapper exports it as `GSTACK_CAPY_REMOTE_VERSION` only after a fetch that succeeded in this run). A fail trailer never carries notes. Match `^gstack: ok ` or `^gstack: fail `; never parse past the first space after the version. Exit 0 only with `gstack: ok`. Read the exit status and the trailer directly: ```bash out=$(bash /scripts/install.sh --check --for autoplan 2>&1); rc=$? printf '%s\n' "$out" | grep '^gstack: ' [ "$rc" -eq 0 ] ``` Never pipe `--check` (or the installer) through `tail`, `head` or `grep` alone: the pipe's exit status replaces the doctor's, and a silent failure is the incident this installer exists to end. Every `FAIL ` row carries a `fix:` clause and a reason code with its [troubleshooting anchor](troubleshooting.md#install-check-gstack-doctor---check-and-the-capy-installer). A parent agent confirms each subagent machine with this grep; one machine's install never covers another, and a subagent's first report carries its own `gstack:` line. `SKIP pins (planning-only; repo pins differ: ..., launch bun )` (trailer note `pins: skipped (planning-only)`) is not a failure: planning workflows (`--for autoplan`, the plan reviews, `office-hours`) never run the target repo's tests. Workflows that do (`review`, `ship`, `qa`) and `--require` keep `pins` required. When a lane reports to another thread (a handoff, a `gstack-lane-check` result, a coordinator contract), store and use the full thread id (`jam_…`) in the message, never the short display code: the code is a label the app renders, the id is what another agent can address and what the evidence bundle records. `UBI_OWNER` and lane ownership records take the same full id. ## Browser The default install prints `SKIP browser (lazy; gstack-browser-ensure installs on first use)` and downloads nothing. The first browser skill run (`/browse`, `/qa`, `/design-review`, ...) calls `bin/gstack-browser-ensure` from its preflight, which installs Chromium (~115 MiB, about 86 s on a Capy machine) under setup's lock and prints `BROWSER_OK` or `BROWSER_UNAVAILABLE `. `--with-browser` installs it during the install instead, and the installer then prints the planning-only install time and the browser bootstrap time separately. ## The launch line Every successful run, `--check` included, prints before the trailer: ``` GSTACK_LAUNCH: PATH=:$PATH GSTACK_SESSION_KIND=unattended GSTACK_STATE_ROOT= GSTACK_EPHEMERAL=1 GSTACK_CAPY=1 GSTACK_INSTALL_VERDICT= GSTACK_LAUNCH_PATH: GSTACK_LAUNCH_FILE: .env ``` `GSTACK_LAUNCH_FILE` names the same assignments as a sourceable script (mode 0600, beside the verdict under the state root; the PATH prepend is idempotent). The file is part of the ok verdict: a passing run that cannot write it exits 1 with `gstack: fail launch-file — could not write the launch file ; fix: make writable, or set GSTACK_STATE_ROOT to a writable directory (LAUNCH_FILE_UNWRITABLE)`, and a failing run prints no `GSTACK_LAUNCH_FILE` line and removes the file an earlier passing run left beside the verdict it rewrites (a profile check that keeps a fuller ok verdict leaves that verdict's file). `. ` at the start of a shell applies them without ever printing them, which is the only way to hand them to a child agent: pass the path, never the values. None of the assignments is a secret, but during GSTA-96's unattended /ship a docs-audit child re-typed them as `export $(...)`; the substitution came back empty, the bare `export` printed the whole environment, and three API key values landed in its tool output. Never `export $(...)`, `env`, `printenv`, a bare `set` or a bare `export` in a tool fence (`test/launch-env-echo-ratchet.test.ts` keeps them out of every skill fence and shell helper), and the redaction engine now refuses the VALUE of any `*_KEY`, `*_TOKEN`, `*_SECRET`, `*_PASSWORD`, `*_PASSWD`, `*_PASS`, `*_PAT`, `*_APIKEY` or `*_PWD` variable in the writer's environment (any case; names carrying a `PUBLIC` word are framework-public and exempt, gstack's own `GSTACK_BRAIN_LOCK_TOKEN` is not a secret) (`env.value`, HIGH), so a leaked value cannot reach a PR body, receipt, report or Drive file. There is no path exemption: a path-valued `SSL_KEY` that must appear in a text goes through `bin/gstack-redact --allowlist` or the pre-push hook's committed `.gstack-redact-allowlist`; the other writers (`gstack-post`, receipts, reports) take no allowlist, so for one of those commands set the variable empty for that command alone (`SSL_KEY= gstack-post …`): an empty value is below the length floor. `PATH=:$PATH` and `GSTACK_LAUNCH_PATH` appear only when the certified Bun, or the project's resolved Node, is not first on PATH (older Capy images ship `/usr/local/bin/bun` 1.3.14; 57 gstack commands start with `#!/usr/bin/env bun`). `` is never a shared bin directory: a Bun from gstack's store launches from its own version directory, and any other Bun, or a Node the launch needs, launches through `.launch`, a gstack-owned directory holding only `bun`, `bunx` and, when needed, `node`, `npm`, `npx` and `corepack` symlinks. So a Bun found in `/usr/local/bin` or `$BUN_INSTALL/bin` never puts that directory's other tools (the image's node) in front of PATH. The pregate's lanes launch through the same directory. A child shell's `export` cannot configure the parent's later commands, so the parent supplies these assignments to every gstack command it launches by sourcing the launch file (`. `) at the start of the shell that runs it. A fresh shell without them keeps reporting the Bun it would really run: `FAIL runtime — bun 1.3.14 at /usr/local/bin/bun ...; verified bun 1.4.2 found at ~/.bun/bin (not on PATH); fix: export PATH="$HOME/.bun/bin:$PATH" or prefix commands with the GSTACK_LAUNCH line`. `GSTACK_EPHEMERAL=1` tells gstack the disk vanishes with the machine, so the doctor's `state root` row reads `durable=no` and unattended runs say `learnings: skipped` instead of writing silently. A durable root is opt-in: set `GSTACK_STATE_ROOT` to a directory that outlives the machine. The installer never writes to the Capy drive. See [docs/state-root.md](state-root.md#ephemeral-machines). `GSTACK_CAPY=1` turns on install admission. An unattended session (any host) refuses to start a workflow on a Bun below the supported minimum; with `GSTACK_CAPY=1`, an unattended or `GSTACK_SESSION_KIND=spawned` session also needs a passing verdict for this checkout, its current HEAD and a workflow at least as wide as the one starting. A refusal prints `INSTALL: fail ...` and `GSTACK_RESULT: skill= status=refused run=-` and exits 3 ([INSTALL_UNVERIFIED](troubleshooting.md#install-unverified)). Ambient spawned sessions (OpenClaw) only warn. `GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1` admits anyway and says so. ## Bake into a Capy snapshot Machines that never run a workflow should not pay the install (about 30 s cold). A project that runs gstack on most machines can bake it instead: the project Setup's initialize step runs ```bash git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.local/share/gstack ~/.local/share/gstack/bin/gstack-capy-install --require claude,codex,patch ``` and snapshots capture `$HOME` (the checkout, `~/.bun/bin`, both host profiles). Each session then pays only the `--check`. A baked revision still moves forward at install-mode session start whenever it is behind origin, and the baked `~/.bun/bin` reaches workflows through the `GSTACK_LAUNCH` line. The project Setup is shared by every thread in the project, so changing it is the project's decision. ## Working under a Captain In a Capy session (`GSTACK_CAPY=1`, set by the launch line) every workflow prints `CAPY: on` and a `capy-asks` block: decisions go through Capy (a crew thread asks its Captain, otherwise the person), plan approvals are rendered with `gstack-owner-brief render --captain`, answers are recorded with `gstack-approvals`, and /ship's PR body opens with before/after images (`gstack-pr-shots`), Why and Approvals. The guide is [`capy/skill/gstack/captain.md`](../capy/skill/gstack/captain.md); `GSTACK_CAPY_ASKS=0` drops the block for one session. ## Raising the supported minimum `capy/support.env` holds `GSTACK_MIN_REVISION` and `GSTACK_CAPY_MIN_WRAPPER`. Raise `GSTACK_MIN_REVISION` only for install-honesty or security fixes, at stamp time, to the VERSION being released: it is the floor below which the trailer fails and the bootstrapper itself fast-forwards (a checkout above it that is behind origin is fast-forwarded by the checkout's own wrapper, so the floor is not what keeps machines current). It never exceeds VERSION (test/capy-skill-wrapper.test.ts). Raise `GSTACK_CAPY_MIN_WRAPPER` together with `GSTACK_CAPY_WRAPPER` in the bootstrapper when the bootstrapper itself changes in a way older copies cannot work with; older Drive copies keep working and print an owner action ([CAPY_WRAPPER_STALE](troubleshooting.md#capy-wrapper-stale)), and the doctor's `wrapper` row warns (an active copy below the minimum is a WARN, never a FAIL; `bin/gstack-upgrade` never leaves a pending reinstall for it). Wrapper 2 only adds optional exports (the trailer's `latest:` note and the `cloned` line), so the minimum stays 1. ## Exit codes | Exit | Meaning | |-----:|---------| | 0 | `gstack: ok` — every required component passed | | 1 | `gstack: fail ` — a required component failed; each row has a fix. Also `gstack: fail launch-file …` when a passing run could not write its launch file ([LAUNCH_FILE_UNWRITABLE](troubleshooting.md#capy-install-launch-file-unwritable)) | | 2 | usage error | | 3 | preflight stopped before `./setup` (`INSTALL_PREFLIGHT_FAILED`, `RUNTIME_PIN_CONFLICT`); nothing was installed |