Files
gstack/docs/troubleshooting.md
T
+22 8bb6d65270 v1.91.66.0 fix: /autoplan without permission prompts after background reviewers, 34 contributor PRs from the backlog triage (#3087)
* docs: fix CONTRIBUTING.md test-tiers link in TESTING_INTERNALS

The link resolved relative to docs/, pointing at a nonexistent
docs/CONTRIBUTING.md. Adopts #3070.

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

* ci: refresh GitHub Actions SHA pins (setup-buildx 4.4.1, build-push 7.4.0, attest 4.2.2, setup-node 7.0.0)

Rewrite of dependabot #2988. Every bumped `uses:` stays pinned to the full
commit SHA (verified against the upstream tags) and now carries its exact
version comment, including the lines that had none. setup-node 6.5.0 -> 7.0.0
is a major bump; its notes list an ESM migration, cache outputs and removal of
a dummy NODE_AUTH_TOKEN export, none of which affect our node-version-only use.
actionlint 1.7.11 (CI image) and 1.7.12 are clean.

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

* docs(openclaw): stop advising a per-spawn env for spawned sessions

OpenClaw's sessions_spawn has no env argument (openclaw/openclaw#51654
was closed without adding one), so the documented
env: { OPENCLAW_SESSION: "1" } silently did nothing. Point users at the
ACP harness or Gateway environment instead and guard the old advice.
Adopts #2749.

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

* fix(benchmark): scope /benchmark routing to web page performance

/benchmark claimed the bare words "benchmark" and "performance", so model
and eval benchmark requests could route to the web performance skill
instead of /benchmark-models. Narrow the description, Use-when list and
triggers to web-specific phrases, point model/skill benchmarks at
/benchmark-models, and split the root router line the same way. The
catalog lead stays within the discovery budget ("Web performance ...").

Adopts #2302 and closes duplicate #2083 (rewritten on current main).

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

* fix(llms.txt): link skills by generated path, drop host-skipped skills

gen-llms-txt built every skill link as <name>/SKILL.md, so the router
pointed at a nonexistent gstack/SKILL.md (it is the root SKILL.md) and
/claude-code pointed at a SKILL.md the Claude host never generates.
Links now use the template's generated output path, and skills the
Claude host skips are left out of the index. Adopts #2279.

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

* fix(ci): install Bun from the SHA-256-verified release archive in GitLab CI and Ubicloud setup

Rewrite of #1713. .gitlab-ci.yml's .setup-bun anchor and
scripts/ubicloud/setup-free-suite.sh still piped https://bun.sh/install into
bash. Both now download the bun-v$BUN_VERSION linux-x64 release zip, check it
against the same SHA-256 Dockerfile.ci uses (#1706) before unzipping, install
it to ~/.bun/bin and assert `bun --version`. test/ci-image-cli-pin.test.ts runs
curlPipedToShell over both files and binds their hash and BUN_VERSION to
Dockerfile.ci's amd64 arm and package.json's engines floor, so a Bun bump has
to move all of them together. Verified by running the GitLab anchor in
debian:stable-slim (bash and sh) and the Ubicloud block in a temp HOME.

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

* fix(browse): viewport auto unpins a pinned viewport without a restart

`viewport WxH` pinned the page with no way back short of `browse restart`,
which also drops a headed /connect-chrome session. `viewport auto` (aliases
`reset`, `unpin`) now undoes the pin:

- headed: the context already launches with `viewport: null`, but Playwright
  cannot drop a page's emulated size (and re-applies it on new frame
  sessions), so the active tab gets a fresh page in the same context at the
  same URL or load-html content. Cookies and storage persist, the tab id and
  ownership stay, refs reset as on navigation, and the page follows the real
  window again.
- headless: back to the 1280x720 launch default, tracked viewport included.

`auto` with --scale is rejected. Fixes #1059. Adopts #2782 and #1881.

Co-authored-by: Jack Thomas <17882510+loulanyue@users.noreply.github.com>
Co-authored-by: Jayesh Betala <jayesh.betala7@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(browse): screenshot accepts --clip=x,y,w,h and --selector=<css>

Agent-generated commands often use the GNU `--flag=value` form, which threw
"Unknown screenshot flag". Both value flags now accept it, through the same
validation and conflict checks as the space-separated form. Adopts #1563.

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

* fix(benchmark): price Codex cached input at the cache rate, stop calling Gemini OAuth ready

Codex reports cached_input_tokens as a subset of input_tokens, but the
adapter billed all of input_tokens at the full input rate. Split them
into disjoint uncached and cached counts (what estimateCostUsd expects)
and show new+cached input in the table and Markdown reports. A stored
Gemini OAuth file alone is now reported as not ready, with a reason
naming GEMINI_API_KEY; key-based auth stays ready. Adopts the focused
subset of #2283; the capacity error code and stdin change are left out
(no captured failure or reproduced hang).

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

* fix(review): catch loosened-input consumers and stale user-facing strings

The Enum & Value Completeness check fired only on newly added values, so a
diff that widens what a validator, parser or guard accepts, and breaks an
unchanged downstream consumer outside the diff, was a blind spot. Widen the
check's trigger (heading unchanged) and add a consumer-grep bullet. Teach
the maintainability specialist to flag user-facing strings whose guarding
condition changed while the text did not.

Adopts #2141 (rewritten on current main; the section heading is kept).

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

* fix(global-discover): bucket Codex sessions by originator

Every Codex rollout counted as one undifferentiated codex bucket, so
/retro global could not tell interactive Codex Desktop work from
codex_exec runs or Claude Code-driven calls. Normalize
session_meta.payload.originator into desktop, exec, claude_code or
other (missing, unknown and non-string values), report the split per
repo as codex_originators and globally as tools.codex.originators, and
print it in the summary. Existing totals are unchanged. Adopts #1488.
Refs #1315.

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

* fix(hook-check): cover skill frontmatter hooks, sourced helpers, conflict markers and the caller's tsconfig

Rewrite of #3066. bin/gstack-hook-check only listed setup's hooks plus
/autoplan's by name, so the /careful and /freeze PreToolUse checks (also run
by /guard and /investigate), /plan-ceo-review's hand-off hook and the helpers
they source were never parse-checked before Claude Code ran them.

- The hook list is setup's _hook_command_path arguments plus every gstack path
  on a `command:` line in a generated */SKILL.md frontmatter (9 hooks, up from
  6). The generated files are read rather than the .tmpl sources because the
  /autoplan and /plan-ceo-review commands come from resolvers.
- Each shell hook's gstack-tree helpers (`.`/`source` lines, assignments and
  shellcheck source= directives, followed transitively) are parsed too:
  careful/bin/hook-extract.sh and bin/gstack-state-root.sh for /careful and
  /freeze.
- The shim, its helpers, each TypeScript entry and every local module the
  bundle used (bun build --metafile; entry only when bun lacks it) are scanned
  for conflict-marker lines, which still parse inside heredocs, strings and
  template literals.
- bun build runs from the gstack root, so the caller's broken tsconfig.json or
  bunfig.toml no longer fails every TypeScript hook. Lists are newline
  separated, so a root with spaces works.

Tests in test/setup-hook-syntax-gate.test.ts cover each case; the gstack-doctor
fixture now copies autoplan/SKILL.md, whose frontmatter registers the hook its
hooks-row test breaks. docs/troubleshooting.md describes the wider check.

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

* fix(review): extend defensive-security framing to the security specialist and Red Team

The "authorized defensive-security review" framing and fixture summary mode
lived only in the adversarial subagent prompt. The security specialist and
the Red Team also reason adversarially over the full diff, so repos whose
test corpus is attack payloads could still get those passes refused.

Move both paragraphs into scripts/resolvers/defensive-review.ts (the
adversarial render stays byte-identical), state them once in the specialist
dispatch for the security specialist and the Red Team, and have the Red
Team prompt start from them. Testing and the other specialists keep full
fixture reads; the codex host still strips Review Army. Parity caps for
ship and review rise by the measured 1,380 bytes each (carve-guards notes).

Adopts #1921 (rewritten on current main).

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

* fix(team-init): make the required-mode hook shell-neutral (node CommonJS, no shell variables)

Rewrite of #2217. Required team mode wrote a bash check-gstack.sh and
registered it as "$CLAUDE_PROJECT_DIR/.claude/hooks/check-gstack.sh", which
only works when the hook runner is a POSIX shell. Under cmd.exe or PowerShell
(Windows, Copilot CLI) the variable is not expanded and the .sh cannot run, so
an intended allow or deny became a hook error.

- The hook is now .claude/hooks/check-gstack.cjs: the same root search
  (GSTACK_ROOT, every host's global root, the migrated repo), with the home
  directory from HOME, then USERPROFILE, then os.homedir(). It prints {} when
  gstack resolves and the PreToolUse deny JSON with exit 2 when not.
- The registered command is `node -e "require(require('path').resolve(
  process.env.CLAUDE_PROJECT_DIR || '.', '.claude', 'hooks',
  'check-gstack.cjs'))"`: no `$` or `%`, so sh, cmd.exe and PowerShell all
  pass it through unchanged.
- An existing check-gstack registration (matcher Skill or Skill|skill) is
  replaced in place; duplicates are dropped and unrelated hooks in the same
  entry kept. The legacy .sh is deleted only after the settings write
  succeeds (a tracked one joins the suggested git add), and is kept when bun
  is missing or the write fails, which now reports instead of claiming success.

Tests: team-mode and the hook-schema test run the command as registered;
test/team-init-hook-shell-neutral.test.ts runs it through the platform's
default shell (cmd.exe on the Windows lane, which curates it in) and through
PowerShell when installed (verified here in the pwsh 7 container). The
routing-probe check for the bash `_GSTACK_ROOT` gate now pins the same
contract in the .cjs: every host root probed, block only when none resolves.

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

* fix(setup-gbrain): register the gbrain MCP through bun.exe on Windows so no console window opens

Rewrite of #2007, folding #1963's MCP notes. On Windows the local stdio
registration stored the bun `gbrain` binstub; gbrain.exe starts bun in a new
console, so every Claude Code session opened a visible terminal for the MCP
(the contributor's process trees show it; bun.exe launched directly attaches
to Claude Code's headless ConPTY).

Under MINGW/MSYS/Cygwin the skill now resolves BUN_ROOT from BUN_INSTALL or
~/.bun and registers `$BUN_ROOT/bin/bun.exe <global node_modules
gbrain/src/cli.ts> serve`. That path also covers a `bun link`ed clone, whose
global node_modules entry links to it; gstack-gbrain-install records no other
clone location. When bun.exe or the entry is missing it registers
`$BUN_ROOT/bin/gbrain.exe` by its full name, because Claude Code does not
apply PATHEXT to a stored path (#1963). POSIX keeps the binstub line. The
skill also notes that `claude mcp list` can show "Failed to connect" while a
live session holds a local PGLite brain's single-writer lock (#1963).

test/setup-gbrain-windows-mcp.test.ts runs the generated fence under bash with
stub uname/claude and a temp HOME for MINGW64, MSYS with BUN_INSTALL, a
missing entry, and Linux. It is listed as Windows-incompatible (POSIX stubs);
the console behavior needs a manual Windows check.

Co-authored-by: Max Hogan <max@maxhogan.dev>
Co-authored-by: maxpetrusenkoagent <280464918+maxpetrusenkoagent@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(hosts): rewrite every "Agent tool" phrasing for agent-runtime hosts

The Agent-tool rewrite for hermes, gbrain, openclaw and factory keyed only
on "use the Agent tool", but templates mostly say "via the Agent tool",
"with the Agent tool", "multiple Agent tool calls" or "Use the Agent tool",
so the Claude tool name leaked into those hosts' skills and sections. Add
ordered rules after the existing key (possessive, "the Agent tool", then
bare "Agent tool") mapping to sessions_spawn (gbrain/openclaw),
delegate_task (hermes) and delegation (factory). No goldens change.

Adopts #1935 (rewritten on current main, without the new goldens).

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

* fix(setup-deploy): Vercel auto-deploys only when connected to the Git repo

/setup-deploy stated that Vercel always deploys on push, which is false for
CLI-only projects with no Git integration, and its nothing-detected path did
not say why such a project is invisible to detection. Qualify the Vercel
trigger (ask for and record the CLI deploy command otherwise) and note that
`vercel link` creates .vercel/ so a re-run detects the project.

Adopts the Vercel accuracy part of #2992 (rewritten on current main); the
third-party bring-up tool pointer is not adopted.

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

* fix(retro): persist recommendations so the next retro detects follow-through

/retro produced "3 Things to Improve" but never saved them in its JSON
snapshot, so the next retro could not tell whether last week's
recommendations were acted on. Step 13 now drafts the items before saving
and always writes a `recommendations` array ({category, text}, [] when
empty); Step 12 scores each prior recommendation addressed/partial/open
against this window's commits, files and metrics and renders a
follow-through line, skipping silently for snapshots without the key. The
report format ties the items to the saved array. Global mode is unchanged.

Adopts #1853 (rewritten on current main). Refs #1834.

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

* fix(global-discover): count the interactive Codex CLI (codex_cli_rs) in its own cli bucket

* feat(developer-profile): --reconcile backfills office-hours tenure from timeline.jsonl

sessions[] is written only at office-hours Phase 4.5, so runs that ended
earlier never counted toward tenure and the tier undercounted. Add a manual
`--reconcile` that scans projects/*/timeline.jsonl for office-hours
`completed` events and appends a count-only row ({date, mode, project_slug,
signal_count: 0, signals: [], backfilled: true}) for each run with no
sessions[] entry for the same project within 60 minutes, extended back by a
duration_s under 8h. It is idempotent, skips malformed lines, and leaves the
file untouched on a no-op. `--read` counts backfilled rows for SESSION_COUNT
and TIER but takes LAST_*, DESIGN_*, CROSS_PROJECT and the nudge from logged
sessions only. Not wired into the preamble yet; the office-hours greeting
guard for backfill-only profiles is left to the office-hours owner.

Adopts #2668 (rewritten on current main). Closes part (b) of #2657.

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

* fix(autoplan): the publication hook approves /autoplan's own Reads after the skill turn

Claude Code applies a skill's allowed-tools only to the turn that invoked it.
On 2.1.292 the default fork-subagent mode runs reviewers in the background, and
the completion notice starts a new turn, so later Reads outside the working
directory (the next phase's section file, the close packet) raised permission
cards. Skill hooks stay registered for the session, and a PreToolUse "allow"
skips the card (reproduced in a PTY on 2.1.292).

autoplan/bin/owned-read.ts approves a parent Read only when the guard itself
returned no output, and only for this installation's autoplan/sections/*.md
(canonical path or the gbrain :user render) or an immutable phase artifact in
<repo>/.gstack/tmp/autoplan/autoplan-<phase>-*/ whose repo is the session's
project directory or contains it. Denials, unverified allows, child calls and
every other path are unchanged. renderSectionBase and userRenderRoot move
there with it.

Tests that asserted a verified driver Read as {} now expect the approval;
autoplan-owned-read covers the shipped shim, the close packet without a
journal, verified artifacts through the guard, look-alikes (foreign install,
writable copy, symlink alias, other repository, other names) and a guard
denial staying a denial.

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

* fix(autoplan): restore point and phase artifacts live in the project's .gstack/tmp/autoplan/

Skill hooks do not run inside subagents, so a reviewer launched after a
background completion notice raised a card for its own input (native-prompt.md
under the state root). Step 1 now puts RESTORE_PATH in the repository's
git-excluded .gstack/tmp/autoplan/ (added to .git/info/exclude, mode 700), and
the snapshot tool writes every phase artifact beside it, so /autoplan and its
reviewers read them from the working directory without a card. The guard's
bindings are unchanged: they anchor on the restore point's directory. Step 1
still prints the slug, which the old RESTORE_PATH carried.

A free test runs the generated Step 1 block twice from a repository
subdirectory: the restore point lands in <repo>/.gstack/tmp/autoplan/, the
exclude line is written once and git status stays clean. The autoplan parity
cap rises by the measured bytes (115,270 / 101,979 = 1.1303).

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

* test(evals): autoplan-guard-pty fails on any permission card after the reviewer's notice turn

The scripted boundary now mirrors Step 1's layout (restore point and artifacts
in the project's .gstack/tmp/autoplan/) and continues past the Phase 2 entry to
dispatch the Phase 2 reviewer after the CEO reviewer's completion notice. The
case asserts four verified guard allows, that the close packet Read, the Phase 2
section Read and the Phase 2 dispatch came after a task-notification turn, that
the Phase 2 reviewer read its input, and that no permission card appeared at
all. Still one trial on haiku, about 75-85 seconds.

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

* docs(autoplan): permission cards after a background reviewer, and where /autoplan keeps its files

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

* fix(outside-review): read a trailing '— Medium.' or '(High)' severity on a finding line, the shape Codex now writes

* feat(design): honor OPENAI_BASE_URL for every design API call

One resolver, openaiUrl() in design/src/auth.ts, now builds every OpenAI
URL the design binary, its prototype and its live model check use. Setting
OPENAI_BASE_URL sends image generation and screenshot analysis to an
OpenAI-compatible gateway; unset or blank keeps api.openai.com. The
prototype script now goes through receiptedFetch, so its egress is
receipted like the rest, and receipts already record the gateway host.

A source scan keeps any design file outside auth.ts from hardcoding an
OpenAI API URL again, and a request-shape test proves the gateway reaches
both the vision and image endpoints.

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

* review: testing specialist treats negative assertions as decisions

A new assertion that something is absent records a behavior removal and
needs cited intent. A rename or removal in the diff can empty a standing
negative assertion so it passes forever; the specialist now greps the suite
for negative assertions naming the old value.

Adopted from the testing-specialist part of a community PR; the scope-check
hunk targeted text that no longer exists.

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

* review: security specialist checks authorization a route-guard read misses

Four access-control classes where the guard is correct and the object still
leaks: error pages composed outside the guard, ownership surviving a role
downgrade, list/detail/download paths checked as one, and a session mutated
before the identity-switch callback is validated.

Adopted to review/specialists/security.md only; the /cso hunk targeted a
bullet list that is now an OWASP/ASVS table.

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

* review: zero findings is valid; suppress six common false positives

The checklist now says a clean diff may come back clean, and suppresses
framework-owned error handling, long exhaustive switches, null derefs a
type guard already narrows, N+1 on fixed-cardinality loops, hardcoded test
fixture values and non-cryptographic Math.random().

The contributor's "missing input validation on an internal function" and
"missing await on fire-and-forget" suppressions are left out: both can hide
real bugs.

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

* docs(todos): parked contributor ideas by theme; /autoplan permission follow-ups

* design-review: capture code tokens before universal principles

/design-review read only DESIGN.md and otherwise fell back to universal
principles, so a project whose system lives in CSS custom properties,
tailwind.config or a theme file got audited and mocked up against an
invented aesthetic. The skill now reads code tokens before falling back,
and the target-mockup brief reuses the captured colors, fonts, radii and
spacing, changing layout only.

The contributor's optional microcopy checks (domain vocabulary, human
register) are not adopted here.

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

* feat(sidebar): add a terminal text-size picker to the toolbar

The sidebar terminal toolbar gets an XS-XL text-size picker beside
Restart. SM (13px) stays the default, so nothing changes until a user
picks a size; the choice persists in chrome.storage.local and applies
to the live terminal, re-fitting xterm and re-sending the PTY size two
frames later so the new character cell is measured first.

A VM test runs the real sidepanel-terminal.js through one auto-connect
and checks the creation size, the stored-size fallback, persistence and
the live resize.

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

* feat(design-shotgun): generate with Codex's built-in $imagegen on Codex

On the Codex host, /design-shotgun now generates and edits mockups with
Codex's documented $imagegen skill in its default built-in mode (the
image_gen tool), so users signed in with ChatGPT need no OPENAI_API_KEY.
The design binary stays for the local comparison board only, images are
published with gstack-design-claim as before, and screenshot evolution
loads the captured page with view_image before an $imagegen edit.

Step 3c-3d moved into a DESIGN_SHOTGUN_GENERATION resolver that returns
the existing design-binary text unchanged for every other host; the
Claude output differs only in two host-neutral rewordings of the
evolve-from-screenshot pointers. DESIGN_SETUP and the regenerate step of
the board loop gain matching Codex-only text for design-shotgun.

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

* voice: ban the "load-bearing" stock phrase

Models overuse "load-bearing"; it joins the AI-vocabulary ban line in both
voice tiers. The tier-2 voice directive is also tightened (the meta
"compressed for runtime" tag and the bounded closer's restated exemption
clause) so the shared preamble shrinks by 163 bytes per tier-2+ skill,
which also pays for the reply-language rule that follows. The ship goldens
are refreshed for the new render.

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

* voice: reply in the language of the user's latest message

Every skill's voice directive now tells the agent to reply in the language
of the user's latest message unless asked otherwise, so long English skill
text no longer drags a Chinese (or any non-English) session into English.
Mixed-language sessions follow the latest message. Code, commands, paths,
identifiers and quoted output stay verbatim, and at tier 2+ so do the
machine-read question markers (D<N>, option letters, (recommended)).

The rule costs 205 bytes per tier-2+ skill and 144 per tier-1 skill. A
further 25-byte cut to the bounded closer's exemption list leaves the net
for this wave at +17 bytes per tier-2+ skill and +158 per tier-1 skill.
review, qa and browse caps rise with measured notes; the ship goldens are
refreshed.

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

* feat(redact-prepush): committed exact-span allowlist at .gstack-redact-allowlist

The pre-push hook never fed the engine's allowlist option, so a reviewed,
known-benign value (a public dev-compose URL, a documented sample key) warned
or blocked every push, and the only ways out were a bypass or reshaping code
until the detector stopped matching.

The hook now reads the repo-root .gstack-redact-allowlist from each pushed
commit (never the working tree), one exact matched span per line with #
comments. Only a finding whose matched text equals an entry is suppressed: a password alone, a
substring, or a different key still reports and still blocks, and engine.*
unscanned findings are untouched. Every push that carries entries prints the
entry count and each suppressed finding's rule and file line. Oversized files
(over 64 KiB) are ignored. gstack.redact.allowEmail keeps working as before.

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

* feat(brain-sync): opt-in removal publishing with a bulk-removal valve

gstack-brain-sync never published a removal: a deleted synced file, or the
old directory after a project slug migration, stayed on the remote forever
and gbrain indexed both copies.

New setting artifacts_sync_removals (off by default). When on, each drain
commits and pushes, receipted like every push, the removal of tracked files
that are gone from disk and match the sync allowlist. Transcript pages are
never removed here, and a decision log goes only together with its active
snapshot. One valve: a drain that would remove more than 20 files at once
publishes none and says so. `gstack-brain-sync --publish-removals` lists the
pending removals and `--publish-removals --yes` publishes them on demand,
whatever the setting.

Co-authored-by: Jey2311 <5177215+Jey2311@users.noreply.github.com>

* feat(windows): setup.ps1 checks prerequisites and runs ./setup in Git Bash

A PowerShell entry point for Windows users. It checks that Git for Windows,
Bun and Node.js are on PATH (printing the winget command for each missing one
and changing nothing), finds Git Bash from git.exe rather than a PATH bash
that may be WSL's, and runs ./setup from the checkout with every argument
passed as its own argv entry to a constant bash script. No argument is
interpolated into shell source. ./setup itself is unchanged.

Co-authored-by: Jim McKeeth <jim@mckeeth.org>

* v1.91.63.0: CHANGELOG, version

* fix(ci): link bunx after the SHA-verified Bun install in GitLab CI and the Ubicloud setup

* fix(redact-prepush): strip every parent column from combined-diff lines, so a merged release bump is exempt like any other

* fix(hook-check): treat a Windows drive path as absolute (cygpath when present), so --list finds the skill hooks from a native caller

* fix(windows): owned-read requires the read-only attribute on Windows; setup.ps1 passes arguments NUL-separated through a private file; Windows-only tests expect the owned-read approval

* perf(hook-check): bundle every hook's TypeScript entries in one bun process instead of two bun starts per entry (seconds each on Windows)

* perf(hook-check): read all skill frontmatter in one awk and scan conflict markers with one grep per hook (half the process starts)

---------

Co-authored-by: thearbajonart <334330110+thearbajonart@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: DanCanadian <211164803+DanCanadian@users.noreply.github.com>
Co-authored-by: Nader Helmy <31414549+creatornader@users.noreply.github.com>
Co-authored-by: Nader Helmy <creator.nader@gmail.com>
Co-authored-by: Zee Zheng <228040220+spacegeologist@users.noreply.github.com>
Co-authored-by: Jack Thomas <17882510+loulanyue@users.noreply.github.com>
Co-authored-by: Jayesh Betala <jayesh.betala7@gmail.com>
Co-authored-by: techcenter68 <75930234+techcenter68@users.noreply.github.com>
Co-authored-by: Mattias Petersson <mattias.petersson@gmail.com>
Co-authored-by: Arno V <38716+aversini@users.noreply.github.com>
Co-authored-by: 0xDevNinja <manmit0x@gmail.com>
Co-authored-by: Benjamin D. Smith <7134745+BenjaminDSmithy@users.noreply.github.com>
Co-authored-by: Brian Majewski <1693720+bmajewski@users.noreply.github.com>
Co-authored-by: neallee2012 <1499497+neallee2012@users.noreply.github.com>
Co-authored-by: Max Hogan <max@maxhogan.dev>
Co-authored-by: maxpetrusenkoagent <280464918+maxpetrusenkoagent@users.noreply.github.com>
Co-authored-by: katlun-lgtm <264247399+katlun-lgtm@users.noreply.github.com>
Co-authored-by: mikehasa <91774998+mikehasa@users.noreply.github.com>
Co-authored-by: Briac <137724549+briacSck@users.noreply.github.com>
Co-authored-by: JackJin <1037461232@qq.com>
Co-authored-by: EmilianoU26 <314541067+EmilianoU26@users.noreply.github.com>
Co-authored-by: frosimanuel <frosimanuel@gmail.com>
Co-authored-by: Tom Lam <tomlam0811@berkeley.edu>
Co-authored-by: Yotam <yotsurfing@gmail.com>
Co-authored-by: Jinzhe <hwang.phy@gmail.com>
Co-authored-by: Xianying Tan <shrektan@126.com>
Co-authored-by: Boyard Engels <boyard.engels@gmail.com>
Co-authored-by: Jey2311 <5177215+Jey2311@users.noreply.github.com>
Co-authored-by: Jim McKeeth <jim@mckeeth.org>
2026-10-08 17:40:06 +00:00

1266 lines
56 KiB
Markdown

# Troubleshooting gstack messages
Find the message you saw, then follow its fix. Every check gstack runs ends in
one of three states:
- **ran**: the check executed. It may still have found problems.
- **not run**: gstack chose not to run it (you turned it off, or nothing applied).
- **unavailable**: gstack tried and could not run it. This is missing coverage,
never a pass.
A message that is not `ran` has this shape:
```
<check>: not run (<reason>). Fix: <command>
<check> unavailable: <reason> (<detail>). No review ran; this is missing coverage, not a pass. Fix: <command>
<check>: ran, verdict unverified (<reason>). Fix: read the output above
```
Search this page for the words after `unavailable:` or `not run (`. Each
section has a stable link anchor; the reason codes and anchors come from
`lib/gate-outcomes.ts`, and a free test fails if a code has no section here.
<a id="gstack-doctor"></a>
### Check readiness before a skill runs
Run `~/.claude/skills/gstack/bin/gstack-doctor` (other hosts: `./setup --status`
in your gstack checkout prints the doctor's absolute path). It prints one row
each for the install, state root, Bun, hooks, Codex, the cached Codex model
check, artifacts sync, the browse bundle, the other compiled binaries, the
/cso native helper, Claude Code, the largest session journal and recent
/autoplan guard codes. Each row is `ok`, `warn`,
`not configured` or `fail` with its fix; only `fail` exits non-zero. It makes
no paid call: the Codex rows report the cached model check and its age, and
`--live` runs that check once. Paste its output into bug reports.
---
## Model policy
The [model-policy guide](model-policy.md) explains the two tiers, all six
settings, precedence and the read-only inspection command. A resolved selection
is not proof that the requested provider or model can run.
<a id="model-policy-config"></a>
### Model policy configuration is invalid or unreadable
**Meaning.** The new policy path could not read a coherent configuration or a
configured value is invalid. An unreadable file is not treated as an absent
setting: doing that could silently select the premium default.
**Fix.** Repair the file or key named by the error. `plan_review_tier` accepts
`frontier`, `smart` or `host`; `implementation_tier` accepts `frontier` or
`smart`. Use `gstack-config unset <key>` to restore a default, not an empty
value. A rejected `set` leaves the old value intact. Correct ownership/read
permissions rather than deleting an unrelated state directory.
For a malformed line such as `plan_review_tier = smart`, edit the reported file
and correct or remove that line first: `unset` only removes canonical `key:`
records and cannot repair a misspelled delimiter or quoted key.
**Expected result.** `gstack-models list` or the corresponding `resolve`
command explains the winning source. No outside model call ran on this error.
<a id="model-policy-provider"></a>
### A custom provider needs an explicit model or host mode
**Meaning.** Native settings or environment flags route the reviewer to a
custom endpoint or partner platform. A public API catalog identifier is not
necessarily a valid deployment identifier there. Malformed native provider
configuration also prevents gstack from assuming the public endpoint.
**Fix.** Supply a compatible model for that request, set the provider's gstack
environment override or tier-model setting, or choose
`gstack-config set plan_review_tier host`. Keep credentials in the provider's
normal authentication mechanism, never in a model setting. If the error names
a malformed native configuration, fix that source first.
**Expected result.** Inspection reports an explicit selection or honest
host-managed delegation. It does not certify account entitlement or send a
public default to an inferred custom endpoint.
<a id="model-policy-selection"></a>
### The selected review model could not be used
**Meaning.** The requested model was rejected or the provider could not
complete the review. A model may be nonexistent, unavailable to the account,
retired, or behind a misconfigured endpoint; preserve the provider's evidence
rather than treating all of these as the same diagnosis. No successful outside
review is implied, and gstack does not substitute a different model.
**Fix.** Change the source named in the error. For example, an environment
override beats a tier setting, so editing the tier cannot replace it. Unset or
correct that override, or explicitly name a supported model for the request.
Follow the existing auth, quota, sandbox or timeout repair when that is the
reported failure. Missing usage identity remains unknown, even if inspection
showed a requested ID.
**Expected result.** The next invocation announces its model and source,
checks that same selection and reports the actual provider result. A cached
probe or a native fallback is not a completed outside review.
<a id="model-policy-freshness"></a>
### Model-catalog freshness is unknown, stale or needs an update
**Meaning.** Official evidence changed, a required source could not be
checked, or the retained evidence does not match this catalog and parser.
`unknown/source-unavailable` is not current. A later source failure must not
erase an earlier retirement finding or close its maintenance issue.
**Fix.** Read the failed source and evidence details in the freshness workflow
report and tracking issue. Repair the source parser or publication access when
needed, then run the workflow manually against the default branch. If a model
was superseded or deprecated, follow the [maintainer checklist](model-policy.md#maintainer-update-checklist)
to qualify a replacement or record a supported mitigation. Do not clear the
issue just because a new release has not been adopted yet.
**Expected result.** Complete source checks produce evidence bound to the
current catalog, while unresolved lifecycle findings remain visible. Personal
pins and unrelated PRs are unchanged. Check the workflow's last run date too:
a scheduled job that never runs cannot report its own absence.
#### Repairing `recoveryRequired` state
This flag deliberately keeps the issue open until a maintainer repairs the
record. Another successful fetch alone cannot certify corrupted history.
1. Save the current issue body, including its retained recovery archive, and
download the latest trustworthy workflow report. Use its `report.state` as
the starting state; do not invent run IDs, catalog hashes or check dates.
2. Reconcile its `lifecycle` array with the issue's independent lifecycle block
and any retained archive. Preserve known retirements, deadlines and human
dispositions. A model currently in the catalog must not keep an old
`resolvedByCatalog` flag from a previous replacement.
3. In the issue editor, restore valid JSON between the `state:begin` and
`state:end` marker lines. Put that same lifecycle array between
`lifecycle:begin` and `lifecycle:end`. Restore the standalone ownership and
region markers, and leave human text outside the owned region intact.
4. Set `recoveryRequired` to `false` only after that reconciliation. If prior
successful evidence cannot be verified, set `lastSuccess` to `null` rather
than making up a fresh success. If lifecycle history cannot be recovered,
leave the flag set and the issue open for investigation.
5. Run **Model-policy freshness** against the default branch. Only complete,
current evidence with no unresolved findings can close the issue. A parsing
or source error leaves it open; do not clear history to manufacture a pass.
## Outside reviews (Codex and Claude Code)
Outside reviews send your diff, plan or question to a second AI provider. Their
verdict comes from `lib/outside-review-result.ts`, which prints
`VERDICT: clean|findings|unverified|unavailable`. A `findings` verdict with a
P0 or P1 finding blocks exactly like a native P0/P1. `unverified` and
`unavailable` are missing coverage: /ship and /review continue, show the gap in
the readiness dashboard and the PR body, and never count it as a pass.
<a id="outside-review-verdict"></a>
### How the verdict is read from a review
- **Severity tags.** `[P0]`-`[P3]` (or Codex's native `P1:` labels). P0 and P1
block; P2 and P3 are advisory.
- **Severity words in label position.** `Severity: High`, `Priority: low`, a
line that starts with `High:`, `High —`, `[High]` or `**High**` (after an
optional heading, bullet or number), a bold `**High**` anywhere, or a table
cell `| high |`. Critical and High block like P0 and P1; Medium and Low are
advisory like P2 and P3. Words inside prose do not count: "high-level",
"low-risk", "a medium-term follow-up" and "no critical findings" are not
findings.
- **No findings.** A review with no tag and no label is `clean` only when it
says so explicitly (`NO_FINDINGS`, "no issues", "did not find any bugs").
Otherwise it is `unverified` (see below), never `clean`.
- **Design proposals** (the design-direction voices in /design-consultation
and /office-hours) are read with the `proposal` gate: a completed proposal
needs only its `Recommendation: ... because ...` line.
Outside-review prompts ask the reviewer to label each finding Critical, High,
Medium or Low, so most reviews land on `findings` or `clean`.
<a id="sourced-helper-location"></a>
### `gstack: cannot load gstack-codex-probe` / `gstack: cannot locate <helper> (shell: ...)` / `CODEX_MODE: helper_unavailable`
**Meaning.** Skills run `gstack-codex-probe` as a command, one subcommand per
check, so the shell your agent uses does not matter. `cannot load
gstack-codex-probe` and `CODEX_MODE: helper_unavailable` mean the probe file is
missing or not executable in your install.
`cannot locate <helper> (shell: ...)` comes from a helper that is still loaded
into your shell with `source` (`gstack-egress-lib.sh`, and the Codex probe when
a skill rendered before the upgrade sources it). Such a helper finds its own
directory from bash (`BASH_SOURCE`) or zsh (`%x`). In any other shell (dash,
sh), or when the shell cannot say which file it is reading, it stops instead of
guessing a path. The message names the shell it saw.
**Fix.** For `cannot load`, re-run `./setup` from your gstack checkout (or
`/gstack-upgrade`). For `cannot locate`, run the skill from bash or zsh (the
macOS and Linux defaults), or tell the helper where gstack is installed:
```bash
export GSTACK_ROOT=~/.claude/skills/gstack # your install dir; it holds bin/
```
If you see `gstack: sourcing gstack-codex-probe is deprecated ...`, a skill
rendered before the upgrade is still sourcing the probe. It keeps working
until a release on or after 2026-10-21; run `/gstack-upgrade` to re-render
your skills now.
**Expected result.** `~/.claude/skills/gstack/bin/gstack-codex-probe select-model exec`
prints `CODEX_SEL: <model>` (and `CODEX_MODEL: <model> (exec; source: ...)` on
stderr) from any shell, and preflights print a `CODEX_MODE` other than
`helper_unavailable`. `gstack-codex-probe help` lists every subcommand.
<a id="codex-sandbox-unavailable"></a>
### `Codex outside review unavailable: Codex's sandbox could not start here (...)`
**Meaning.** Codex runs every command inside a Linux sandbox (bubblewrap). In
many containers and devcontainers the kernel does not allow unprivileged user
namespaces, so the sandbox cannot start and every command Codex tries fails.
gstack reports this instead of trusting a review that read nothing.
`CODEX_MODE: sandbox_unavailable` is the same condition found by the free
preflight before any paid call.
**What is kept.** Nothing was sent for review, or the review was discarded. Your
code and files are unchanged.
**Fix.** Enable unprivileged user namespaces for the container (for Docker, a
seccomp profile that allows them), or, inside a container you trust, run
Codex without its sandbox for this shell:
```bash
export GSTACK_CODEX_NO_SANDBOX=1
```
**Expected result.** With namespaces enabled, the next review prints
`OUTSIDE_STATUS: completed`. With `GSTACK_CODEX_NO_SANDBOX=1`, every review
prints `WARNING: GSTACK_CODEX_NO_SANDBOX=1: ...` because Codex can then read
and write anything your user can. Only the exact value `1` works, and only from
your shell environment.
<a id="outside-review-commands-failed"></a>
### `... outside review unavailable: the reviewer could not run commands or read the diff (...)`
**Meaning.** The reviewer tried to run commands but none succeeded, or its
answer says it could not run commands or read the diff. A "no issues found"
written after that is not a review.
**What is kept.** Nothing; the answer is shown above but not counted.
**Fix.** Read the reviewer's stderr above, repair what it names, and re-run.
**Expected result.** `OUTSIDE_STATUS: completed` with a `VERDICT:` line.
<a id="outside-review-execution-failed"></a>
### `... outside review unavailable: the reviewer process failed (exit N: ...)`
**Meaning.** The provider CLI exited non-zero. The first stderr line is in the
parentheses.
**What is kept.** Partial output is shown above; it is not counted.
**Fix.** Repair the cause in the provider diagnosis (log in again, fix the
model, check the network), then re-run.
**Expected result.** `OUTSIDE_STATUS: completed`.
<a id="outside-review-timeout"></a>
### `... outside review unavailable: the reviewer hit its time limit and was stopped (exit 124)`
**Meaning.** The provider did not finish within its deadline and was stopped.
**What is kept.** Partial output is shown above; it is not counted.
**Fix.** Re-run with a smaller scope, or check the provider's status and your
network.
**Expected result.** `OUTSIDE_STATUS: completed`.
<a id="outside-review-empty-response"></a>
### `... outside review unavailable: the reviewer returned no response`
**Meaning.** The provider exited successfully but returned nothing.
**Fix.** Read the stderr above, then re-run.
**Expected result.** `OUTSIDE_STATUS: completed`.
<a id="outside-review-refused"></a>
### `... outside review unavailable: the reviewer declined to review`
**Meaning.** The provider answered with a refusal instead of a review.
**Fix.** Re-run; if it declines again, rely on the native review.
**Expected result.** `OUTSIDE_STATUS: completed`.
<a id="outside-review-missing-markers"></a>
### `... outside review unavailable: the response lacks the markers this gate requires (...)`
**Meaning.** The answer did not contain what the gate checks: a
`Recommendation: ... because ...` line, a `SCORE:`/`AMBIGUITIES:` pair, or
severity tags.
**Fix.** Re-run the review.
**Expected result.** `OUTSIDE_STATUS: completed`.
<a id="outside-review-unverified"></a>
### `... outside review: ran, verdict unverified (...)` / `OUTSIDE_STATUS: unverified` / `GATE: UNVERIFIED`
**Meaning.** The review completed but had no severity tag or label and gave
no explicit no-findings conclusion, so no pass or fail can be read from it
([how the verdict is read](#outside-review-verdict)).
**What is kept.** The full answer is shown above.
**Fix.** Read the output above and decide. It is not a pass, and /ship and
/review list it as missing coverage.
<a id="codex-model-unusable"></a>
### `CODEX_MODE: model_unusable` / `MODEL_UNUSABLE`
**Meaning.** Codex rejected the selected model: the account cannot use it
(HTTP 400), the model is retired, or a custom provider's `base_url` is wrong
(HTTP 404). The `CODEX_MODEL:` line names the model and where it came from.
**Fix.** Choose a model your account can use, or correct the provider:
```bash
export GSTACK_CODEX_MODEL=<supported-model>
# or edit model / base_url in ${CODEX_HOME:-~/.codex}/config.toml
```
**Expected result.** `CODEX_MODE: ready`.
<a id="codex-quota-exhausted"></a>
### `CODEX_MODE: quota_exhausted` / `MODEL_QUOTA_EXHAUSTED`
**Meaning.** Codex refused the call because the account behind it hit its
usage limit (`You've hit your usage limit`, or `insufficient_quota`). The line
under the marker is Codex's own message, with its reset time and where to buy
more. The model choice is fine. gstack reports outside coverage as
unavailable, never as a pass, and caches the result for 15 minutes, so the
rest of the run (and other skills) make no Codex call. The HINT line says how
many minutes remain.
**Fix.** Wait for the reset time in Codex's message, or add credits or a
higher plan for that account. To use a different account, sign in again:
```bash
codex login
```
To re-check before gstack's 15-minute cache expires (for example, right after
buying credits), skip the cached result for one check, or delete it:
```bash
export GSTACK_CODEX_PROBE_RETRY=1 # unset it again afterwards
# or
rm -f ~/.gstack/.codex-model-probe # <state root>/.codex-model-probe
```
**Expected result.** After the reset (or after `codex login`, which changes
the auth signature and re-probes at once), `CODEX_MODE: ready`.
<a id="codex-rate-limited"></a>
### `CODEX_MODE: unverified (rate_limited)` / `MODEL_PROBE_RATE_LIMITED` / `unavailable: Codex rate-limited the review`
**Meaning.** Codex answered HTTP 429 (too many requests), which usually clears
within seconds. It is a different state from `quota_exhausted` and is never
cached. At probe time the review still runs, and its own result decides. A 429
during the review itself means that review failed, so coverage is missing,
never a pass.
**Fix.** Re-run the review in a minute. If 429s persist, check the rate limits
for the account or API key on the provider's dashboard.
**Expected result.** `CODEX_MODE: ready`, and the review completes.
<a id="codex-mode-unverified"></a>
### `CODEX_MODE: unverified` / `MODEL_PROBE_INCONCLUSIVE`
**Meaning.** The short model check timed out or hit a network error, so gstack
could not confirm the model works. The review still runs and its own result is
checked.
**Fix.** Nothing now. If the review then fails, its message says why.
<a id="codex-auth-failed"></a>
### `CODEX_MODE: not_authed` / `AUTH_FAILED`
**Meaning.** No Codex credentials were found: no `CODEX_API_KEY`, no
`OPENAI_API_KEY`, no `auth.json`, and no set environment variable named by a
custom provider's `env_key` in `config.toml`.
**Fix.**
```bash
codex login
```
or export the variable your provider's `env_key` names.
**Expected result.** `CODEX_MODE: ready`.
<a id="outside-review-disabled"></a>
### `Codex review skipped (codex_reviews disabled)` / `CODEX_MODE: disabled`
**Meaning.** You turned outside reviews off. This is `not run`, never a pass and
never an outage.
**Fix.** To turn them back on:
```bash
gstack-config set codex_reviews enabled
```
<a id="codex-review-notice"></a>
### `NOTICE: gstack outside reviews send the review prompt and code to Codex (...) using ...`
**Meaning.** Outside reviews are on by default. The first one on a machine
says which provider receives your prompt and code and which login or key pays
for it. It shows once (gstack records `.codex-review-notice-shown` in its state
directory) and never blocks the review.
**Fix.** Nothing, if that is what you want. To stop sending code to Codex:
```bash
gstack-config set codex_reviews disabled
```
**Expected result.** Later reviews print `Codex review skipped (codex_reviews disabled)`
or `CODEX_MODE: disabled`, and /ship and /review show the outside review as not run.
---
## /ship, /review and /document-release
<a id="ship-no-version-source"></a>
### `Shipped without a version change: no version source is configured (no VERSION file, no .gstack/version-path). ...`
**Meaning.** /ship found no version file it owns: there is no `VERSION`, no
`.gstack/version-path`, or release automation (release-please, Changesets,
semantic-release, a workspace monorepo, a placeholder package.json version)
owns the version. This is `not run` for the version bump.
**What is kept.** Everything else ships. No bump, no CHANGELOG version header,
no `vX.Y.Z` PR title prefix.
**Fix.** To have /ship version releases, create `VERSION`, or pin the file:
```bash
echo package.json > .gstack/version-path && git add .gstack/version-path
```
**Expected result.** The next /ship bumps that file and writes a CHANGELOG header.
<a id="ship-version-source-broken"></a>
### `gstack-version-bump: classify: version source is broken: <path> ...`
**Meaning.** The pinned version file does not exist, is empty, unreadable, or
contains no parsable version. /ship stops instead of inventing `0.0.0.0`.
**What is kept.** Nothing was written.
**Fix.** Repair that file, or correct `--version-path` / `.gstack/version-path`.
**Expected result.** `gstack-version-bump classify` prints the current version.
<a id="review-base-stale"></a>
### `BASE_REFRESH: stale <rev>` / `Base coverage: stale at <rev>`
**Meaning.** /review could not fetch the base branch (offline, no credentials,
or a read-only `.git` such as Codex's sandbox), so it reviewed against your
local `origin/<base>` at `<rev>`.
**Fix.** Run `git fetch origin <base>` outside the sandbox and rerun /review if
the base has moved.
<a id="review-fingerprint-tmpdir"></a>
### `gstack-wtree: cannot create a temp index under <dir>; set TMPDIR to a writable directory` / `gstack-review-log: cannot create a private object directory under <dir>; no working-tree fingerprint. Set TMPDIR to a writable directory.`
**Meaning.** /review fingerprints the working tree in a private temporary
object directory (Codex's sandbox keeps `.git` read-only). The temp directory
could not be created, so this review has no fingerprint and later steps cannot
reuse its evidence.
**Fix.** `export TMPDIR=<a writable directory>`, then run /review again.
<a id="review-fingerprint-stage"></a>
### `gstack-wtree: cannot stage the working tree: <git error>`
**Meaning.** The fingerprint's objects could not be written. Standalone
`gstack-wtree` writes into the repository's object store.
**Fix.** Run it through /review (`gstack-review-log` supplies a private object
directory), or make `.git/objects` writable.
<a id="plan-audit-not-run"></a>
### `Plan completion audit: not run (no plan is bound to this branch and no docs/designs/ file matches). Fix: ...`
**Meaning.** /ship and /review audit a PR against the plan bound to its branch
(plan mode, /autoplan, or a `Plan: <path>` line in the PR body). With none bound
and no matching `docs/designs/` file, they no longer pick the newest unrelated
plan; the audit is `not run`.
**Fix.** Add `Plan: docs/designs/<file>.md` to the PR body, or run /autoplan.
**Expected result.** The next /ship or /review prints the audit against that plan.
<a id="learnings-bun-missing"></a>
### `gstack-learnings-search: bun not found on PATH, so learnings could not be read. Fix: ...`
**Meaning.** Learnings search (and `gstack-timeline-read`, "the timeline") needs
Bun. It exits 127 instead of looking like "no learnings".
**Fix.** Install Bun from https://bun.sh, then `cd <gstack checkout> && ./setup`.
**Expected result.** Skills print your prior learnings again.
<a id="learnings-unavailable"></a>
### `LEARNINGS: unavailable (<first stderr line>)`
**Meaning.** A skill's learnings lookup failed (often Bun is missing). Skills
used to show nothing here, which looked like "no learnings".
**What is kept.** Your learnings file is untouched; the skill continues without them.
**Fix.** Read the reason in parentheses. If it names Bun, install it and re-run
`./setup`.
<a id="log-tmpdir-unwritable"></a>
### `gstack-learnings-log: could not create a temp file in <dir>, so the learning was not recorded. Fix: ...`
**Meaning.** The learnings, question and preference logs write through a temp
file in `${TMPDIR:-/tmp}`. A sandbox that blocks it used to drop the entry silently.
**Fix.** `export TMPDIR=<a writable directory>` and log again.
<a id="plan-tune-not-calibrated"></a>
### `CALIBRATION: not calibrated: no recorded signals`
**Meaning.** Your logged answers matched no psychographic signal, so /plan-tune
has nothing to calibrate from. It no longer says "calibrated".
**Fix.** Answer more registered questions, then `gstack-developer-profile --derive`.
<a id="skill-start-former-bucket"></a>
### `gstack: earlier data for this project is in projects/<slug> (a worktree's old bucket). Merge it: ...`
**Meaning.** Linked worktrees now share the main checkout's project identity.
Learnings and checkpoints a worktree wrote under its own old bucket still exist.
**Fix.** Merge them (logs are de-duplicated, conflicts listed, nothing overwritten):
```bash
gstack-slug --adopt-legacy --from <slug>
```
Or stop the reminder: `gstack-slug --adopt-legacy --dismiss <slug>`.
---
## Design skills
<a id="design-not-available"></a>
### `DESIGN_NOT_AVAILABLE: <path> --version exited <code>` (or `timed out after 10s`, `is not installed`, `no timeout/gtimeout/perl ...`)
**Meaning.** Design skills now run `"$D" --version` with a 10-second deadline
before claiming `DESIGN_READY`. Exit 137 means macOS killed the binary at launch
(a bad code signature); setup prints the same condition as
`design unavailable: <bin> is killed at launch (exit 137) after re-signing`.
**What is kept.** Nothing was generated or spent.
**Fix.** Re-run setup, which rebuilds and re-signs the binary:
```bash
cd <gstack checkout> && ./setup
```
If the message names a missing timeout tool, install coreutils (`gtimeout`) or perl.
**Expected result.** The next design skill prints `DESIGN_READY`.
<a id="design-image-model-invalid"></a>
### `GSTACK_DESIGN_IMAGE_MODEL="<value>" is not a gpt-image model name (expected something like gpt-image-2); fix it or unset it to use gpt-image-2`
**Meaning.** The design binary refuses an image tool model override that is not
a `gpt-image-*` model name, before sending any request.
**Fix.** `export GSTACK_DESIGN_IMAGE_MODEL=gpt-image-2` (or another gpt-image
model your key can use), or `unset GSTACK_DESIGN_IMAGE_MODEL`.
<a id="design-taste-profile-unavailable"></a>
### `TASTE_PROFILE_UNAVAILABLE: could not resolve the project slug (gstack-slug failed). Fix: run ./setup.`
**Meaning.** /design-consultation and /design-shotgun load your taste profile.
This says it could not be found, instead of acting as if you had none.
**Fix.** `cd <gstack checkout> && ./setup`.
<a id="design-variant-save-failed"></a>
### `cannot save paid image to <path>: <code> (<cause>). Image bytes were received; saved a recovery copy to <tmp path>. Fix: ...`
**Meaning.** The paid image generation succeeded but writing it to the output
path failed (disk, permissions, or every name through `-999` taken). gstack never
buys the same image twice and never overwrites an existing image.
**What is kept.** The image, at the printed recovery path in your temp directory.
**Fix.** Follow the printed fix (free disk space, make the folder writable, or
pick a new `--output`), then copy the recovery file to where you want it.
---
## Codex and other env-var hosts
<a id="gstack-no-install-found"></a>
### `gstack: no install found (tried <path>). Fix: ./setup --host <host> from your gstack checkout; ./setup --status shows it.`
**Meaning.** On Codex, Factory, OpenCode, Cursor, Copilot and Kiro every bash
block finds gstack on its own: an exported `GSTACK_ROOT` (with `bin/` and
`lib/`), then a repo-local install, then the host's global install
(`$CODEX_HOME/skills/gstack` on Codex). None existed.
**Fix.**
```bash
cd <gstack checkout> && ./setup --host codex # or your host
./setup --status
```
**Expected result.** `./setup --status` lists the install and says the router is
a real file and its section links resolve.
<a id="setup-status-router-symlink"></a>
### `router is a symlink, which Codex skips` / `section links: N of M broken (first: ...)`
**Meaning.** `./setup --status` checks each registered install. Codex ignores a
symlinked SKILL.md, and a broken section link means a carved skill cannot load
its next step.
**Fix.** `cd <source> && ./setup --host <host>` (the command is printed).
<a id="setup-skill-copy-saved"></a>
### `saved your edited <path> to <backup> (setup rewrites gstack's SKILL.md copies on every run)`
**Meaning.** Installed router and nested SKILL.md files are now real copies,
rewritten by every setup. Your hand edit was saved first.
**What is kept.** Your edit, at `<backup>`. Move the customization into your own skill.
<a id="setup-skill-copy-backup-failed"></a>
### `error: could not back up an edited SKILL.md copy under <root>; the previous runtime root is unchanged`
**Fix.** Fix permissions on `<state root>/backups`, then `./setup --host <host>`.
<a id="upgrade-not-a-checkout"></a>
### `ERROR: <dir> is not a gstack checkout; nothing was changed. Re-run Step 2.` / `INSTALL_DIR is not set: re-run Step 2 ...`
**Meaning.** /gstack-upgrade now checks, before any git command, that it is
inside gstack's own checkout. A block ran without the path Step 2 printed, so it
stopped instead of touching your project.
**Fix.** Run Step 2 again and prefix the block with `INSTALL_DIR=<printed path>`.
<a id="skill-over-size-limit"></a>
### `<host>/<skill>/SKILL.md is <bytes> bytes, over the 160,000-byte limit. Fix: carve sections with usesLazySections() for this skill.`
**Meaning.** For contributors: hosts read at most 160,000 bytes of one SKILL.md.
`bun run gen:skill-docs` fails in the repo and warns under `--install-root`.
---
## Setup and auto-update
<a id="bun-too-old"></a>
### `gstack needs Bun 1.3.3 or newer (1.4.2 recommended); found <version> at <path>. Nothing was installed or changed.`
**Meaning.** Bun older than 1.3.3 silently ignores the build flags that stop
gstack's compiled tools from reading a project's `.env`, so setup refuses it
before writing anything. The same link appears on two warnings that do not stop
setup: `warning: gstack is tested on Bun 1.4.2 (CI pin); found <version>` (1.3.3
up to 1.4.2 works but is untested) and `warning: could not read the Bun version
(...)` (setup continued; check `bun --version`).
**Fix.**
```bash
bun upgrade
./setup
```
**Expected result.** `bun --version` prints 1.4.2 or newer and setup finishes
with no Bun warning.
<a id="auto-update-bun-too-old"></a>
### `gstack auto-update: update held (bun-too-old: found Bun <version> at <path>; gstack <version> needs <floor> or newer); nothing was installed or changed. Fix now: ...`
**Meaning.** Team-mode auto-update fetched a release whose setup needs a newer
Bun than the one setup would run. It left your checkout and installed skills at
the current revision and prints this line once per session start. (An older
auto-updater running its first update cannot make this check.)
**Fix.** `bun upgrade`; the next session start resumes the update. To update
now: `bun upgrade && cd <gstack checkout> && git pull --ff-only && ./setup`.
<a id="auto-update-incomplete"></a>
### `gstack auto-update: setup did not finish (<reason>); installed skills may be out of date. gstack retries automatically. Fix now: cd <dir> && ./setup`
**Meaning.** The pull worked but setup (or migrations) failed. gstack no longer
says "just upgraded". It retries after 1 hour, then 6, then 24, and prints this
line once per session start. A `(bun not found on PATH)` reason means install
Bun first.
**What is kept.** The previous install stays active.
**Fix.** Run the printed command.
<a id="auto-update-pull-failed"></a>
### `gstack auto-update: git pull did not finish (exit N: ...); gstack is not updating. Fix now: ...`
**Fix.** `cd <gstack checkout> && git pull --ff-only && ./setup`.
<a id="setup-hook-does-not-parse"></a>
### `gstack setup: refusing to register hooks that do not parse (Claude Code would block tool calls with them): <file>:<line>: <error>`
**Meaning.** Claude Code runs gstack's hook shims through `/bin/sh`, and a hook
that does not parse exits 2, which blocks the tool call it guards in every
session. Setup parse-checks every hook it registers and every hook a skill's
frontmatter runs (`/autoplan`, `/careful`, `/freeze`, `/guard`, `/investigate`
and `/plan-ceo-review`): the shim, the gstack shell helpers it sources (such as
`careful/bin/hook-extract.sh` and `bin/gstack-state-root.sh`), and the
TypeScript it runs with its local imports. A merge conflict marker line in any
of those files fails as `unresolved merge conflict marker`, even when the file
still parses (markers inside a heredoc, a string or a template literal do).
The TypeScript is bundled from the gstack checkout, so the `tsconfig.json` or
`bunfig.toml` of the project you run it from is never read. It registers the
hooks that parse, skips the ones listed, finishes the rest of the install, and
exits non-zero. Claude Code runs hooks straight from `~/.claude/skills/gstack`,
so a skipped hook that an earlier setup registered, or that a skill runs,
keeps running the broken file until it is fixed. This is a gstack bug, or a
half-applied edit or merge in your checkout: report the printed
`<file>:<line>`.
**Fix.**
```bash
git -C ~/.claude/skills/gstack status # half-applied edits or merge conflicts?
git -C ~/.claude/skills/gstack checkout -- <file> # or finish the merge
cd ~/.claude/skills/gstack && ./setup
```
**Expected result.** Setup finishes with exit 0 and no refusal line.
<a id="auto-update-hook-does-not-parse"></a>
### `gstack auto-update: update held (hook-does-not-parse: <file>:<line>: <error>); nothing was installed or changed, and your current hooks keep running. ...`
**Meaning.** Team-mode auto-update fetched a release with a hook that does not
parse or still holds a merge conflict marker (the same check setup runs). It
checked the incoming revision before moving your checkout, so your
checkout, installed skills and registered hooks stay at the current revision.
gstack checks again at the next update check and installs the first release
whose hooks parse. This is a gstack bug: report the printed `<file>:<line>`.
**Fix.** Nothing to do locally. A manual `git pull` followed by `./setup`
cannot be checked before the pull; setup then refuses the broken hook (see the
entry above).
<a id="host-renders-pruned"></a>
### `pruned <dir> (<host> is not installed from this checkout): ...` / `kept <path>: not proven generated (...)` / `host-render backup: <dir> (...)`
**Meaning.** A gstack checkout keeps skills only for the agents installed from
it, listed in `.gstack-installed-hosts` in the checkout (#1694). Older installs
rendered every agent's copy, so a global Claude install carried 632 `SKILL.md`
files (34.7 MB) that Claude Code and Cursor-agent scan. setup removed the copy
for an agent that is not installed from this checkout. Generated files were
moved to the backup directory it printed; links into the checkout were removed;
files it could not prove gstack generated (`kept ...`) were left in place.
`prune.log` in the backup lists every path. The same prune runs once from the
upgrade migration. Agents in the record are never pruned.
**Fix.** Nothing, if you don't use that agent. To get an agent back, install
it from this checkout, which records it and renders its skills:
```bash
./setup --host <name>
```
For an instruction-only agent you render by hand (Hermes, OpenClaw, GBrain),
add its name to `.gstack-installed-hosts` first, then run
`bun run gen:skill-docs --host <name>`. To restore one file, `mv` it back from
the backup. A `kept` file is yours: move or delete it when you no longer need it.
The backup sits in gstack's state root, which no agent scans; delete it once
you are sure you don't need it (about 36 MB for a pre-1.91.65 Claude install).
**Expected result.** The next `./setup` prints no `pruned` line, and
`./setup --status` lists the agents you use.
<a id="cso-windows-msvc-compile"></a>
### `CSO unavailable: its native helper was not built (windows-msvc-compile)`
**Meaning.** Visual Studio's compiler was found but the /cso native helper did
not compile. setup prints the first compiler error. It used to say "install
Visual Studio".
**Fix.** Fix the printed compiler error, then re-run `./setup`.
<a id="cso-build-or-publish-failed"></a>
### `CSO unavailable: the native helper's <stage> step failed (...)` / `CSO publish step failed (...) for <revision>; previous CSO kept: <revision>`
**Meaning.** The /cso native helper is optional. Its build prerequisites were
present, but the `build` step (compiling the helper) or the `publish` step
(swapping the new helper into `bin/` under a lock) failed. setup used to stop
here (#3071); now it finishes everything else and says which step failed. When
an earlier helper was installed and the failed publish restored it, setup
prints `previous CSO kept` and /cso keeps using that helper; otherwise /cso
reports `not assessed`. `interrupted` means the step was killed before it could
record a result. The outcome is in `bin/.gstack-cso-build-result`, and
`gstack-doctor`'s `cso` row reads the same record. The build output is in
`bin/.gstack-cso-build.log`.
**Fix.** Read the log, fix what it reports, then retry from your gstack checkout:
```bash
bun run build:cso && ./setup
```
On Windows, setup and the build use PowerShell 7 (`pwsh`) when it is installed
and fall back to Windows PowerShell 5.1. The root cause of the staged files
vanishing during publish in #3071 is still unknown; attach the log there if you
hit it. CI sets `GSTACK_STRICT_BUILD=1`, which makes these failures fatal so
build regressions cannot hide.
**Expected result.** setup prints no CSO line, and the doctor's `cso` row is
`ok`.
<a id="cso-windows-docker"></a>
### `Docker found at <path>, but native Windows Docker transport is not supported yet; static assessment only.` / `docker.exe at <path> is outside the trusted install locations (...)`
**Meaning.** On Windows, /cso looks for `docker.exe` only under the install
folders Windows reports for Program Files, Program Files (x86) and the Windows
directory, by its real path, with no symlink or junction on the way. A
user-writable directory is untrusted, because the Docker child carries
registry credentials; that refusal cannot be overridden.
Even a trusted `docker.exe` cannot run /cso's isolated containers yet: /cso
admits only a local Unix Docker socket, and Docker Desktop on Windows speaks
over a named pipe. /cso reports this and runs its static assessment only; no
container or runtime check runs.
**Fix.** For runtime checks, run /cso from Linux or macOS (WSL2 counts as
Linux) with a local Docker socket. On Windows, static assessment is the
supported mode; if the refusal named a user directory, install Docker Desktop
under Program Files.
<a id="cso-capacity"></a>
### `Snapshot manifest cap exceeded: ...` / `64 MiB source cap exceeded: ...` / `Symlink or special source file: <path>`
**Meaning.** /cso copies the repository into a private snapshot before any
audit starts, and refuses the whole run when the snapshot would be incomplete
or too large. Each message names the cap that applied:
- **Snapshot manifest cap (16 MiB).** One entry per tracked or nonignored
untracked file; about 50,000 files fit.
- **64 MiB source cap.** The full size of every such file outside dependency
and VCS directories counts, including files over 1 MiB whose contents are
withheld from the audit.
- **Symlinks.** A tracked symlink anywhere in the repository stops the run.
**Fix.** No setting raises these caps. Run /cso on a smaller checkout that
holds the code you want audited and no symlinks. Add your file count or size to
[#2993](https://github.com/garrytan/gstack/issues/2993).
<a id="conductor-auq-hook-removed"></a>
### `removed the AskUserQuestion preference hook: it breaks Conductor's native AskUserQuestion (#2207). ...`
**Meaning.** In Conductor, setup no longer installs the plan-tune
question-preference hook and removes one it added before. The other plan-tune
hooks stay.
**Fix.** To keep it anyway: `gstack-config set plan_tune_hooks yes`, then `./setup`.
<a id="browse-extension-id-invalid"></a>
### `Error: browse_extension_id '<v>' is not a Chrome extension ID ... Existing value left unchanged.`
**Fix.** Copy the 32-letter ID (letters a to p) from `chrome://extensions`, then
`gstack-config set browse_extension_id <id>`.
---
## Browser
<a id="browse-runtime-version-skew"></a>
### `[browse] this install's browse CLI (<root>, build <hash>) and the gstack checkout's server bundle (<checkout>, build <hash>) are from different builds, so the server was not started. ...`
**Meaning.** On Windows a host runtime root (`~/.codex/skills/gstack` and the
other env-var hosts) holds a copy of browse with no `node_modules`, so its CLI
starts the server bundle in the gstack checkout recorded in `.source-path`.
The checkout was rebuilt (or updated) without refreshing this runtime root, so
the two builds differ and browse refuses rather than run a mismatched server.
**Fix.** Re-run setup from the checkout named in the message; it rebuilds and
refreshes every runtime root:
```bash
cd <checkout> && ./setup
```
<a id="browse-chain-no-flow"></a>
### `[browse] chain: no flow to run (stdin was empty)` (or `stdin is a terminal`, `stdin could not be read (EAGAIN)`)
**Meaning.** `browse chain` with no arguments runs the JSON flow piped to it.
Nothing arrived, so it exits 1 before starting a browser. It used to exit 0 on
Windows having run nothing.
**Fix.** Pipe the flow, or pass it as an argument:
```bash
echo '[["goto","https://example.com"],["text"]]' | browse chain
browse chain 'goto https://example.com | text'
```
<a id="browse-chromium-path-failed"></a>
### `Chromium at GSTACK_CHROMIUM_PATH=<path> failed to launch: ...`
**Meaning.** Headless browse and make-pdf now use `GSTACK_CHROMIUM_PATH`
(NixOS, macOS 13). That binary could not start.
**Fix.** Point `GSTACK_CHROMIUM_PATH` at a working Chromium, or
`unset GSTACK_CHROMIUM_PATH` to use gstack's bundled one.
<a id="browse-profile-in-use"></a>
### `Headed Chromium profile <dir> is in use by Chromium PID <pid> (started by PID <ppid>: <name>). ...`
**Meaning.** Headed browse keeps one profile per project
(`<project>/.gstack/chromium-profile`). A live browser owns this one, and
browse never kills it. `... is locked by PID <pid> on another host (<host>)`
means the lock came from another machine through a synced folder.
**Fix.** Close that browser, or `export CHROMIUM_PROFILE=<another dir>`. For the
other-host case, stop it there, or remove `<dir>/SingletonLock` once you are
sure nothing uses it.
<a id="browse-profile-per-project"></a>
### `[browse] Headed browse now keeps a profile per project ...`
**Meaning.** First use copies your logins from `~/.gstack/chromium-profile`
when no browser is using it; otherwise it starts fresh and prints how to import.
`browse profiles` lists them, `browse profiles prune --days 30` removes idle ones.
<a id="browse-blocked-address"></a>
### `Blocked: <host> is a cloud metadata or link-local address` / `... resolves to a cloud metadata or link-local address`
**Meaning.** Browse refuses 169.254.0.0/16 (including container credential
endpoints), 100.100.100.200, fe80::/10 and fc00::/7 in every numeric spelling,
including after a redirect or a page-driven navigation. The tab is reset to
`about:blank` and the command fails. This cannot be overridden.
<a id="browse-forbidden-origin"></a>
### `forbidden origin` from the sidebar terminal
**Meaning.** The terminal accepts only gstack's extension. A self-built or forked
extension has a different ID. `BROWSE_EXTENSION_ID` is no longer read.
**Fix.** `gstack-config set browse_extension_id <your 32-letter id>`.
<a id="browse-type-selector-hint"></a>
### `hint: "<word>" looks like a CSS selector`
**Meaning.** Bare `browse type <text>` types into the focused element. The first
word looked like a selector, so it was typed as text.
**Fix.** `browse type --selector '<sel>' <text>` (put text that starts with `--`
after `--`).
<a id="browse-server-node-missing"></a>
### `server-node.mjs not found. Run bun run build` (Windows)
**Fix.** `cd <gstack checkout> && bun run build`.
<a id="freeze-helpers-out-of-date"></a>
### `[freeze] Hook helpers out of date (partial upgrade?)`
**Meaning.** `check-freeze.sh` and `hook-extract.sh` come from different gstack versions.
**Fix.** `cd <gstack checkout> && ./setup` (or `/unfreeze`).
<a id="browse-chromium-pid-unrecorded"></a>
### `[browse] Could not record the Chromium PID (CDP SystemInfo.getProcessInfo: <error>); browse stop cannot reap a surviving Chromium.`
**Meaning.** For a headless Chromium it launched, browse reads the browser's
process id over CDP so `browse stop` can kill a browser that outlives the
server. That read failed or took longer than 2 seconds, so this session's
browser is not recorded. Browsers gstack did not launch are never recorded or
killed.
**Fix.** After `browse stop`, check for a leftover browser with
`ps aux | grep -i chrom` and end it with `kill <pid>`.
---
## Windows
<a id="windows-smart-app-control"></a>
### `Windows blocked compiled gstack binaries at launch (Smart App Control or another application-control policy, #2595)` / `bash: .../browse.exe: Permission denied`
**Meaning.** Known issue (#2595, #2124). gstack compiles five binaries on your
machine with Bun: `browse`, `find-browse`, `design`, `pdf` and
`gstack-global-discover`. They are unsigned, and a binary built on one machine
never earns the reputation Smart App Control accepts instead of a signature, so
Windows 11 with Smart App Control on refuses to start them. Git Bash reports
that as `Permission denied`, which looks like a file-permission problem but is
code integrity (PowerShell says `An Application Control policy has blocked this
file`). setup now runs each binary's `--version` and names the blocked ones and
the skills that need them: the gstack browser fallback (`/browse`, `/qa`,
`/qa-only`, `/design-review`, `/canary`, `/benchmark`, `/pair-agent`,
`/scrape`, `/make-pdf`), the design binary (`/design-consultation`,
`/design-shotgun`, `/design-html`, `/plan-design-review`) and `/retro global`.
Every other skill works. `gstack-doctor` shows the same state in its
`browse bundle` and `binaries` rows. The exact Windows error text has not been
verified on a Smart App Control machine; setup prints the raw first line beside
its classification.
**Fix.** There is no per-file allowlist for Smart App Control. Today's options:
- Run gstack inside WSL (`wsl --install`, then install gstack in the Linux
distro). WSL runs gstack's Linux build, which Smart App Control does not check.
- Or turn Smart App Control off in Windows Security > App & browser control >
Smart App Control settings. On Windows 11 with the April 2026 update you can
turn it back on later without reinstalling Windows; on older builds turning it
off is permanent. gstack's binaries stay blocked whenever it is on.
Signed release binaries are the real fix and are tracked in `TODOS.md`. After
Windows allows the binaries, re-run `./setup`; the message goes away.
**Expected result.** setup prints no "Windows blocked" line, and
`gstack-doctor` shows `ok` for `browse bundle` and `binaries`.
---
## Memory and gbrain
Every message below is also in [docs/gbrain-sync-errors.md](gbrain-sync-errors.md)
with more detail, along with the per-page ingest messages (`FAILED`,
`re-queued`, `quarantined`, reconcile progress, kept-on-this-machine notices).
<a id="gbrain-db-unreachable"></a>
### `database host unreachable (<code> <host>); your gbrain config is unchanged. Fix: check network or VPN, then re-run /sync-gbrain.`
**Meaning.** A DNS or connection error reaching your gbrain database. It used to
look like a broken config, and setup stripped brain features while offline.
**Fix.** Restore the network or VPN, then `/sync-gbrain`.
<a id="memory-ingest-unattributed-failures"></a>
### `[memory-ingest] ERR: gbrain reported N failure(s) it did not attribute to a staged page ... Refusing to advance state.`
**What is kept.** Nothing is marked saved, so nothing is lost. After three such
runs gstack bisects the batch and quarantines the page that breaks it.
**Fix.** Re-run `/sync-gbrain`.
<a id="memory-reconcile-not-run"></a>
### `[memory-ingest] reconcile: not run (...)`
**Meaning.** After upgrading, gstack re-checks transcripts it had marked saved
and re-imports the ones missing from your brain. That check needs a local gbrain.
**Fix.** `/setup-gbrain`, then `gstack-memory-ingest --reconcile` (`--dry-run` to preview).
<a id="memory-ingest-locked"></a>
### `[memory-ingest] ERR: another memory ingest (pid <N>) is writing <state file>; not run.`
**Fix.** Wait for it to finish, then re-run `/sync-gbrain`.
<a id="memory-ingest-state-unsaved"></a>
### `[memory-ingest] ERR: could not save ingest state <path>: <error>.`
**Fix.** Fix permissions or free disk space under your gstack state root, then `/sync-gbrain`.
<a id="gbrain-artifacts-not-indexed"></a>
### `curated artifacts pushed to git, but gbrain source <id> has 0 indexed pages. Fix: gbrain sync --source <id>, then re-run /sync-gbrain`
**Fix.** `gbrain sync --source <id>`, then `/sync-gbrain`.
<a id="gbrain-dream-skipped"></a>
### `dream: skipped — the installed gbrain cannot run only the resolve_symbol_edges phase, and the full dream cycle costs about 35 minutes ...`
**Meaning.** `/sync-gbrain --dream` (and `--full`'s auto-build) now runs only the
call-graph phase. Your gbrain cannot scope it, so nothing ran.
**Fix.** `gstack-gbrain-install`, then `/sync-gbrain --dream`. Or run the full
cycle yourself: `gbrain dream --source <id>`.
<a id="gbrain-cycle-freshness-unknown"></a>
### `call graph for <source>: unknown: installed gbrain does not expose cycle_freshness`
**Fix.** `gstack-gbrain-install`.
<a id="gbrain-path-unavailable"></a>
### `[gbrain-sync] gbrain source <id>: path unavailable (<path>); gstack skips it.`
**Meaning.** A deleted worktree's code source. gstack never removes it on its own.
**Fix.** Preview, then remove the ones gstack can prove belonged to this machine:
```bash
gstack-gbrain-sync --prune-gone-worktrees --dry-run
gstack-gbrain-sync --prune-gone-worktrees
```
---
## Eval reports and pass-rates
These come from the paid eval census reports and `bun run eval:pass-rates`.
The walkthrough for a red census is [docs/evals/census-red.md](evals/census-red.md).
<a id="pass-rates-unknown-flag"></a>
### `eval:pass-rates: unknown flag <flag>` / `eval:pass-rates: unknown case <id> (not an E2E, judge or paid test file id)`
**Meaning.** The flag or case id is not recognized. The command exits 2 before
fetching anything.
**Fix.** `bun run eval:pass-rates --help` lists the flags. `--case` takes a
registry id from `test/helpers/touchfiles-data.ts` or a paid test file path.
<a id="failure-cause-newer"></a>
### `unknown failure_cause "<x>" (written by a newer gstack; update this checkout to read it)`
**Meaning.** A trial record names a failure cause this checkout does not know.
A newer gstack wrote it.
**Fix.** `git pull` (or rebase your branch onto `main`), then rerun the command.
<a id="paid-case-several-owners"></a>
### `--case <id>: registered by <file>, <file>; it needs exactly one`
**Meaning.** More than one paid test file registers the case, so
`test-paid-shards.ts --case` cannot pick one file.
**Fix.** Run the red shard's file directly; the report's `after a repair:` line
prints it: `EVALS=1 EVALS_TIER=<tier> bun test <file>`.
<a id="evidence-too-large"></a>
### `evidence: <artifact> too large to fetch (<N> MB)`
**Meaning.** The slice artifact holding this red's transcript is over 64 MB, so
`eval:pass-rates --run` does not download it.
**Fix.** Download it from the run page, or
`gh api repos/<owner>/<repo>/actions/artifacts/<artifact id>/zip > slice.zip`.
<a id="headroom-alarm"></a>
### `[headroom] <case> session <key>: max <s> of <s> (<pct>) over <n> sample(s), above the 85% cap. Cut work in the skill or fixture; budgets are never raised ...`
**Meaning.** `eval:pass-rates --gate` (the weekly report) found a case whose
slowest session used more than 85% of the timeout it armed. It is one timeout
away from a red.
**Fix.** Cut work in the skill or fixture. Budgets are never raised. Check the
case with `bun run eval:pass-rates --headroom --case <id>`.
<a id="cost-unknown"></a>
### `cost unknown (no billing captured for any of <N> trial(s))` / `cost $<x> known + <N> of <M> trial(s) cost unknown`
**Meaning.** PTY and Codex sessions record no billing, so their cost is
unknown. Only the known sum is shown.
**Fix.** None needed; this is informational.
<a id="cause-provider-stall"></a>
### `· cause provider_stall: no stream event for <N>s ...`
**Meaning.** The session streamed partial messages, then received no event of
any kind for at least 120 seconds while a model request was in flight and no
tool, permission prompt, hook or subagent was outstanding. Under EVAL_POLICY v1
it is still a failed trial.
**Fix.** Inspect it with `bun run eval:pass-rates --run <run id> --case <id>`.
A paid rerun happens only after a repair.
<a id="overlay-wrong-answer"></a>
### Overlay record with `taskCorrect: false` and `answerError` (contract v4)
**Meaning.** The trial completed with a wrong answer. Under overlay contract v4
that is a valid measurement in the comparison; only an overlay-ON wrong answer
fails the case.
**Fix.** Read `answerError` in the trial JSON. No rerun is needed.
<a id="detector-corpora-stale"></a>
### `[detector-corpora] <N> stale entries (refresh from a current census when one fails)`
**Meaning.** Some replay-corpus entries came from an older input series of
their case. This is informational; the corpus still replays them.
**Fix.** When that case goes red in a census, add the new capture to its corpus
and check it with `bun test test/detector-corpus-<case>.test.ts`.
<a id="auq-substance-panel"></a>
### `recommendation substance median <x> < 4 over samples [<a>,<b>,<c>] (boilerplate/weak)`
**Meaning.** The auq-matrix case scores each captured question's recommendation
with a 3-sample judge panel; fewer than 2 of the 3 samples reached 4, so their
median was below 4.
**Fix.** Read the logged samples and the captured question. A fix goes in the
skill text, followed by one diagnostic run of auq-matrix.
---
## Push guard (redaction)
<a id="redact-postgres-default-pair"></a>
### `db.url_with_password` HIGH on a default `postgres`/`postgres` login whose host is a service name like `@db` (pre-push BLOCKED)
**Meaning.** The default `postgres:postgres` pair is exempt only on `localhost`
or `127.0.0.1`. A service name like `@db` says nothing about where the URL ends
up deployed, so it still blocks.
**Fix.** Use `postgres://postgres:postgres@localhost:5432/...` in local and CI
config, or read the URL from an env var. If the credential is real, rotate it.
Bypass once: `GSTACK_REDACT_PREPUSH=skip git push`.
If the URL is a reviewed, public dev-only value, list it in
[`.gstack-redact-allowlist`](#redact-allowlist).
<a id="redact-allowlist"></a>
### `.gstack-redact-allowlist (<n> entries) suppressed <m> finding(s) in this push`
**Meaning.** The pushed commit carries `.gstack-redact-allowlist` at the repo
root. Each line (after trimming; `#` starts a comment) is one exact matched
span: the whole `postgres://USER:PASSWORD@host:port` URL for
`db.url_with_password`, the key for a key pattern, the address for `pii.email`. A finding is suppressed only when its
matched text equals an entry, so a password alone, a substring, or a different
key still reports and still blocks. The hook reads the file from each pushed
commit, never the working tree, and ignores a file over 64 KiB. Every push that
carries entries prints this line and each suppressed finding's file and line.
It works alongside `gstack.redact.allowEmail`, which stays a local setting.
**Fix.** Nothing, if every listed suppression is the reviewed value. Remove an
entry that no longer applies; rotate a credential that is real.
<a id="redact-version-as-ip"></a>
### `pii.ip_public` MEDIUM on a four-part version number
**Meaning.** The line does not declare the value as a version.
**Fix.** Write it as `version: 1.2.3.4`, `"version": "..."` or `v1.2.3.4`.
MEDIUM findings do not block pushes.
<a id="redact-prepush-email"></a>
### `MEDIUM pii.email <file>:<line>` from the pre-push hook
**Meaning.** The pushed lines add an email address that is not yours and not
already in the destination's commit metadata. The hook does not report your
own address (`git config user.email`), addresses listed in
`gstack.redact.allowEmail`, or author and committer addresses (mailmapped too)
from the history of the pushed commits, of the remote tip being replaced, and,
for a new branch pushed to a configured remote, of that remote's tracking refs.
An address only another remote knows still reports. If the hook also printed
`existing-email suppression was limited for this push`, it could not read the
history in 5 seconds, so known authors may be listed too.
**Fix.** Remove the address, or allow it for this repo:
`git config --add gstack.redact.allowEmail <address>`. If it is your own
address, check `git config user.email`. The allowlist only covers `pii.email`;
it never lets a HIGH finding through.