Files
gstack/docs/designs/FIX_WAVE_2026_10_06.md
+8 28f1385eac v1.91.32.0 fix: Codex works from zsh, free text never enters shell commands, honest failures (#3057)
* fix: sourced helpers locate themselves under zsh (#3024)

Skill blocks source bin/gstack-codex-probe and bin/gstack-egress-lib.sh into
the host's shell. Under zsh (macOS default) BASH_SOURCE is empty, so Codex
model selection resolved /../scripts/... and every outside pass reported
CODEX_MODEL: invalid; the egress lib resolved to the caller's cwd. Resolve
the file once at source time (BASH_SOURCE under bash, %x under zsh), and
replace the bash-only ${!name} indirection in the auth probe. A free test
derives every sourced helper from the generated skills and sources each
under bash and zsh.

Co-authored-by: marcosdetry <marcosdetry@users.noreply.github.com>
Co-authored-by: akshaykalia <akshaykalia@users.noreply.github.com>
Co-authored-by: scottgigante-hubflow <scottgigante-hubflow@users.noreply.github.com>
Co-authored-by: woodsonl <woodsonl@users.noreply.github.com>
Co-authored-by: bezdekt <bezdekt@users.noreply.github.com>

* fix: reply and issue templates never pass untrusted text in double quotes (#3046)

greptile-triage's reply APIs posted -f body="<reply text>" while the
templates quote SHAs and diff code in backticks; in double quotes bash ran
each span (replies posted as 'Fixed in .', and diff or reviewer text ran as
a command). The pre-existing-failure issue template and /spec's issue title
had the same shape. All three now go through quoted heredocs. The
generated-bash lint gains untrusted-in-quotes and now also covers runtime
reference docs in skill directories (review/greptile-triage.md).

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix: a Codex usage limit is its own state, not 'inconclusive; proceeding' (#3051)

The model probe matched only model and broken-install errors, so a usage or
rate limit fell through to MODEL_PROBE_INCONCLUSIVE: Codex's own message
(with the reset time) was dropped, the hint pointed at model config, and
every later skill re-probed for 30 s. The probe now returns
MODEL_QUOTA_EXHAUSTED (exit 4) on a failed, non-timeout call that names a
usage limit, rate limit, insufficient_quota or 429, relays Codex's line
verbatim and caches it for 15 minutes. Preflights map it to
CODEX_MODE: quota_exhausted (missing coverage, no further Codex calls), and
classifyOutsideReview reports a mid-run limit as unavailable (quota) with
its own troubleshooting anchor.

Co-authored-by: venkatareddyg <venkatareddyg@users.noreply.github.com>

* test: record ship/review union sizes after the quota branch and issue template

* fix: Codex watchdog freezes the command before reaping its children

The bash-native watchdog (stock macOS, no timeout binary) ran pkill -P on
the command's children and then killed the command. Between the two, the
command continued past its killed child and printed output after its
deadline. Stop it first. The test's minimal PATH also lacked pkill, which
the watchdog uses to reap children (stock macOS ships it), so the orphaned
child held stdout open and the capture waited out its sleep.

* fix(autoplan): an oversize session journal is a hard too_large denial, not a retryable identity failure (#3050)

The guard capped journal reads at 32 MiB and reported a longer journal as
'identity', which the hook treats as deny-and-retry. A journal only grows,
so /autoplan could never enter Phase 1 in a long session, and the
troubleshooting row sent people chasing symlinks. Oversize journals now
report too_large as a hard cause that names the size and the supported
fallback (run the reviews by hand or start a new session).

Co-authored-by: venkatareddyg <venkatareddyg@users.noreply.github.com>

* fix(deps): clear the weekly OSV findings (#3040)

Raise overrides to fixed releases: ip-address 10.7.3 (our own pin was the
vulnerable 10.3.1; reachable through socks in browse's proxy path),
fast-uri 3.1.8, hono 4.13.13, proxy-addr 2.0.8, @modelcontextprotocol/sdk
1.31.0, sharp 0.35.5, and in lib/diagram-render dompurify 3.4.16 (bundled
by mermaid) and source-map-js 1.2.2; the render page is rebuilt. Three
advisories without a usable fix get justified, expiring ignores: braces
(build-time only, excalidraw -> sass -> chokidar; a test checks the page
carries no chokidar code), sprintf-js (roarr's constant-format logger) and
katex (needs pre-existing prototype pollution; fix outside mermaid's range).
osv-scanner with the repo config: no issues found.

* fix(autoplan): name a shell-built snapshot init as the cause of the missing-evidence denial (#3045)

The guard binds a phase entry only to a literal snapshot-init Bash call.
When the model resolved the tool path into a variable, chained or piped the
call, init ran and succeeded but the denial said 'Complete the existing
snapshot init step', which reads as already done, so the run gave up. When
no init binds but a Bash call looks like one, the denial now says the shape
cannot be bound and prints the literal re-run (answers reused:true). The
binding rule is unchanged, and the skill says to run init on its own.

Co-authored-by: CharlesBerg85 <CharlesBerg85@users.noreply.github.com>

* fix(browse): chain reads piped stdin synchronously, so Windows runs the flow (#3039)

On Windows, an awaited Bun.stdin.text() inside the CLI's un-awaited main()
let the process exit 0 before reading stdin: a replayed flow reported
success with nothing sent to the daemon. Read fd 0 synchronously (skipped
for a terminal); empty input now gets the server's usage error and a
non-zero exit.

Co-authored-by: VaibDeveloper <VaibDeveloper@users.noreply.github.com>

* fix(browse): clearer config-mismatch refusal and a WebGL note for the macOS GPU flags (#3030)

A plain call refused because another session started the daemon with
--headed/--proxy was told to 'apply --proxy/--headed', flags it never
passed. It now says who started the daemon and offers both remedies. The
BROWSER.md env table notes that the default macOS GPU flags disable WebGL
and that GSTACK_DISABLE_GPU=off restores it for WebGL QA.

Co-authored-by: Hagetino <Hagetino@users.noreply.github.com>

* fix(ship): skip Greptile triage when no PR exists yet (#3020)

/ship's first run on a branch dispatched a triage subagent only to learn
there was no PR. Step 10 now checks for a PR inline (gh's 'no pull requests
found') and records that Greptile runs on the PR once it exists; other gh
errors still dispatch so the child can report unavailable.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix(router): route only to loaded skills, and say when not to invoke (#3018)

The root router listed every skill and ended 'When in doubt, invoke the
skill'. Current Claude models follow that literally: they tried to invoke
skills a user had turned off in Claude Code's skillOverrides (which gstack's
own disabled_skills render cannot see), and borderline requests tipped into
full workflows. The router now routes only to skills in the session's
available-skills list and names when to answer directly; the CLAUDE.md
routing block skill-start offers says the same.

Co-authored-by: Rekin226 <Rekin226@users.noreply.github.com>

* fix: spawned sessions never auto-grant consent or publish off the machine (#3016)

Spawned sessions auto-choose the recommended option except for destructive
or irreversible choices. Main already withholds the artifacts-sync consent
prompt from spawned sessions; the remaining gap was any other prompt whose
recommended option grants a consent or syncs data off the machine. Every
spawned directive (skill-start's block, the escape sentence, the Conductor
deny reason and the AUQ-error fallback) now names that as irreversible. A
consent the user already gave interactively stays in force.

Co-authored-by: ekson73 <ekson73@users.noreply.github.com>

* test: fix the spawned-block slice and type the #3045 re-init events

* fix(redact): TypeScript types and JSX expressions are code, not env.kv secrets (#3048)

A credential-named parameter or prop with an unquoted code value
(`session: SessionState,`, `key={turn.requestId + turn.role}`,
`apiKey={settings.apiKey}`) cleared the entropy gate and blocked pushes as
a MEDIUM env.kv finding. One rule replaces the PR's two keyword heuristics:
a value that reads as a code name (no digits, word-like camelCase or dotted
parts) with a code terminator, or a brace expression of such names with no
quoted literal, is source syntax. Paired controls keep firing on quoted,
brace-wrapped, concatenated, YAML-bare and random-letter literals.

Co-authored-by: tianxchen <tianxchen@users.noreply.github.com>

* fix(setup): refuse to register a hook that does not parse (#3032)

Claude Code runs gstack's hook shims through /bin/sh; a shim that does not
parse exits 2, which for a PreToolUse hook blocks the tool call in every
session until someone finds the file. Before any hook registration is
healed or written, setup now runs bash -n on each registered shim and a Bun
parse of its TypeScript entry, and stops with the file, line and fix. A
lean rewrite of the PR's idea (one gate block plus a test that runs it).

Co-authored-by: BenjaminDSmithy <BenjaminDSmithy@users.noreply.github.com>

* fix(browse): Windows runtime roots start the server from the source checkout's bundle (#3026)

On Windows a host runtime root (~/.codex/skills/gstack and the other
env-var hosts) holds file copies of browse/dist and no node_modules, so
its server-node.mjs could not import playwright and every browser skill
died (the module-scope server.ts half was fixed in v1.91.19.0). setup now
records the source checkout in every runtime root's .source-path, and the
CLI runs that checkout's bundle when the adjacent one cannot reach
playwright. Verified by unit layout tests and the Windows CI lane, not
live on Windows.

Co-authored-by: ubay887 <ubay887@users.noreply.github.com>

* fix(copilot): on Windows, run bash blocks in Git for Windows Bash (#3047)

Copilot CLI on Windows ran gstack's extensionless helpers straight from
PowerShell, which opened an app picker, and bare bash can resolve to WSL.
The Copilot tool glossary (rendered into every Copilot skill) now says to
run each bash block in Git for Windows Bash by its full path and never to
translate a block into PowerShell. The PR's PowerShell repair skill and
README section are not taken.

Co-authored-by: nareshh74 <nareshh74@users.noreply.github.com>

* test: record ship's union size after the Greptile no-PR check

* fix: keep issue numbers out of model-read Copilot text; seed the new routing sentence in the autoplan fixture

* docs: Oct 6 fix-wave plan, reviewed through /autoplan

* fix(autoplan): every oversize-journal path reports too_large with a recovery that keeps the work

Review follow-up to the #3050 fix. The summed batch read and the narrowed
parent loop now report too_large with the real size instead of a plain
error naming a hard-coded 32 MiB. The denial names the size and the cap,
and gives the recovery that keeps work (/context-save, new session,
/context-restore, /autoplan) with a docs anchor. A test seam that can only
lower the cap replaces the 33 MiB fixture.

* test(autoplan): negative controls and more shapes for the shell-built init denial

* fix(redact): decide TS/JSX source syntax by file context, not by value shape

Review follow-up to the #3048 fix. The word-shape heuristic also exempted
real secrets made of words (API_KEY=VelvetRiverOrbitSunset; in a .env).
The scanner now takes the file path (ScanOptions.sourcePath): the pre-push
hook scans the pushed diff per file with its +++ b/ path, and gstack-redact
--from-file passes its path. Only in a TypeScript/JSX file is an unquoted
value after ':' or a JSX brace of names treated as source; the same line in
.env/YAML, or with no path, is scanned as a literal, and the jwt pattern
still fires in .ts files.

Co-authored-by: tianxchen <tianxchen@users.noreply.github.com>

* docs: gstack's own CLAUDE.md routing block matches the new router wording

Live check (Claude Code 2.1.284): a skill set to "off" in skillOverrides
is absent from the session's skill list while its sibling is listed, and
both are listed without the override, so 'route only to skills in your
available-skills list' holds.

* fix(sync-gbrain): don't offer a call-graph build for a source with no code pages (#3029)

Step 3.5 offered --dream for any source with pages, including one that
holds only markdown (synced with gbrain's default strategy), where a build
cannot help, and the injected CLAUDE.md guidance told future sessions to run
--dream for any count: 0. On gbrain >= 0.60, code-def for an impossible
symbol answers .status (probed live against gbrain 0.60.96: ready for a
code source, out_of_scope for a markdown-only one). Step 3.5 now probes the
pinned source first, reports the cause instead of asking on out_of_scope,
and keeps today's offer when the probe is unknown (older gbrain, an error).
gstack's own sync uses --strategy code, so the PR's soft-delete claim does
not apply to gstack's path.

Co-authored-by: syong <syong@users.noreply.github.com>

* fix(setup): register the hooks that parse, refuse the rest with file:line (#3032)

The landed gate stopped setup before any registration, so one unparseable
hook left every other hook unregistered. On the Claude install the canonical
root is the checkout Claude Code runs hooks from, so there is no staged swap
to abort: setup now registers the hooks that parse, refuses the ones that do
not with file and line, finishes the install and exits non-zero, saying it
is a gstack bug and that a skipped hook keeps running from the checkout.

bin/gstack-hook-check derives its list from setup's _hook_command_path calls
plus the autoplan phase-publication hook (a test keeps them equal), parses
each shim with its shebang's interpreter, and bundles each TypeScript entry
with its local imports (packages external) into a temp dir, so a broken or
missing import, or a missing entry, fails.

Co-authored-by: BenjaminDSmithy <BenjaminDSmithy@users.noreply.github.com>

* fix(auto-update): hold an update whose hooks do not parse, before the checkout moves

bin/gstack-session-update fast-forwards the live checkout before setup runs,
and Claude Code runs hooks straight from it, so setup's gate cannot protect
an auto-update. After the Bun floor check the updater extracts the incoming
revision with git archive and runs bin/gstack-hook-check on it; a failure
holds the update like bun-too-old, records hook-does-not-parse with file:line
in the update status, and the next session start says the current hooks keep
running. Fixture-remote tests cover a broken shim, a broken imported module
and a parseable control.

Co-authored-by: BenjaminDSmithy <BenjaminDSmithy@users.noreply.github.com>

* fix(browse): chain with no flow is a usage error before any daemon boots (#3039)

Empty piped input, a terminal with no arguments, and a readFileSync(0)
error (EAGAIN, EOF) now print the chain usage with the cause and its
troubleshooting link and exit 1 before ensureServer(), instead of sending
an empty argument to a daemon the CLI booted for it. Tests run the real CLI
with no daemon and no browser, plus the stub-daemon flow.

Co-authored-by: VaibDeveloper <VaibDeveloper@users.noreply.github.com>

* fix(browse): refuse a runtime-root CLI and checkout server from different builds (#3026)

A Windows runtime root runs the source checkout's Node server bundle through
.source-path. When the root's CLI and the checkout's bundle carry different
browse/dist/.version builds (the checkout was rebuilt without refreshing the
root), the CLI now refuses to start the server with both builds, the
./setup fix and its troubleshooting anchor. Unversioned dev builds are not
called skew.

Co-authored-by: ubay887 <ubay887@users.noreply.github.com>

* ci(windows): real isolated ./setup install, then a piped chain flow through the installed browse

windows-setup-e2e built the checkout but never ran ./setup. It now installs
the codex host into an isolated HOME (a runtime root copy with no
node_modules), then from an unrelated directory pipes a two-command chain
flow at a loopback page into the installed browse and asserts the page text,
and that empty piped input exits 1 with the usage error. Covers the
checkout-bundle path and the piped-stdin path end to end.

* test(lint): free text never enters a generated command (structural rule)

The generated-bash lint's untrusted-in-quotes rule (gh/glab/git/curl text
flags in double quotes only) becomes free-text-placeholder: any <placeholder>
anywhere in any command, in double, single or no quotes, and in heredoc
bodies (quoted or not), unless it is on the reviewed identifier allowlist
(test/helpers/placeholder-allowlist.ts) with the grammar a value must match.
Quoted-only identifiers (URLs, selectors, paths printed by gstack) must sit
inside quotes. Backslash-newline continuations now read as one command, so
a flag and its value on separate lines are still seen together.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix: reply, issue and spec text travel as agent-written files, never through a heredoc

Replaces the quoted-heredoc bodies with CEO-12's file mechanism: a bash
block creates a private mktemp file in the project's .gstack/tmp/ (kept out
of git through .git/info/exclude), the agent writes the text with its
file-write tool, and the next block posts with -F body=@file, --body-file
or "$(cat file)". A heredoc ends early at any body line equal to its
delimiter; a file has no such line. When the file was never written the
block refuses with the path and the command to send it by hand, and it
deletes the file after a successful post.

- review/greptile-triage.md: replies; the fetched JSON moves from fixed
  /tmp/greptile_*.json paths to a mktemp directory; REPO and PR are
  re-derived per block instead of read from a previous shell.
- Test-failure issues: title and body files, platform case on
  github/gitlab, assignee only when it is a valid login.
- /spec: one draft file feeds the audit hash, every redaction scan, the
  issue and the archive; the archive no longer expands <title>/<body>
  inside an unquoted heredoc; the decision-log JSON is built by jq from
  files.

Location verified against Claude Code 2.1.284 (Dockerfile.ci pin):
project-local .gstack/tmp is writable under acceptEdits with no prompt and
keeps mode 0600; $TMPDIR and the gstack state root prompt even under
acceptEdits.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix(ship): Greptile triage runs only when a PR exists; every other case says why

PR: unknown (gh missing, not logged in, a non-GitHub remote, or a failed
lookup) used to dispatch the triage child anyway. The Step 10 check now
tests gh, its login, the remote and the PR in that order, and every case
but 'PR: exists' skips the dispatch and records
'Greptile: not run (<reason>); runs on the PR once it exists'.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix: question-tuning commands carry no free text

The question log took the summary in single quotes and the preference write
took the user's own words; an apostrophe ended the quote and the rest ran as
shell. The summary is now a kebab-case slug (an identifier with a grammar),
and the optional free_text field is no longer passed from skills (the
binary still accepts it).

* fix(cso): Windows finds docker.exe only under known-folder roots and says transport is unsupported (#3028)

On Windows /cso looked for docker only in Git's directory and System32, so
Docker Desktop was never found and the gap read as 'not installed'. It now
resolves Program Files, Program Files (x86) and the Windows directory through
PowerShell's [Environment]::GetFolderPath (not the process environment),
checks each candidate's real path against those roots and rejects reparse
points. lib/cso/docker.ts admits only unix:/// endpoints, so a trusted
docker.exe reports 'native Windows Docker transport is not supported yet;
static assessment only'; one outside the roots is refused as outside the
trusted install locations, not overridable. Both carry an anchor. Native
named-pipe transport is not implemented.

* fix: sourced helpers share one self-locate block and fail loudly when they cannot locate themselves (#3024 rework)

The codex probe and the egress lib now carry one byte-identical block:
BASH_SOURCE under bash or %x under zsh, made absolute with $PWD, else a set
GSTACK_ROOT that holds the helper. Anything else (dash, sh, a shell that
cannot name the file) prints "gstack: cannot locate <helper> (shell: ...)"
with the docs/troubleshooting.md#sourced-helper-location URL and returns
before any bash- or zsh-only syntax. The Claude-only default path and the
$(pwd) fallback are gone.

The generated Codex preflight dot-sources the probe and, when it cannot load,
reports CODEX_MODE: helper_unavailable with the helper's own line instead of
falling through to not_authed. The class guard enumerates nested sources,
makes real resolver, auth, state-root and egress-home calls under bash and
zsh (absolute, relative then cd, bare name via PATH), covers dash and an
unknown shell, runs the full preflight, and lints BASH_SOURCE to the shared
block plus other bash-only constructs.

Co-authored-by: marcosdetry <marcosdetry@users.noreply.github.com>
Co-authored-by: akshaykalia <akshaykalia@users.noreply.github.com>
Co-authored-by: scottgigante-hubflow <scottgigante-hubflow@users.noreply.github.com>
Co-authored-by: woodsonl <woodsonl@users.noreply.github.com>
Co-authored-by: bezdekt <bezdekt@users.noreply.github.com>

* fix(cso): a Maven or Gradle repository is Java, not Python (#3028)

With no supported stack metadata, preparation defaulted to Python and asked
a Java repository for requirements.txt. A root pom.xml, build.gradle or
build.gradle.kts now names the stack as Java (unsupported for runtime
preparation, so the prerequisite says so), and Python helper scripts or a
root requirements.txt beside it no longer make it Python.

* fix(browse): BROWSER.md says plainly how to enable WebGL; the mismatch refusal links Daemon discipline (#3030)

The GSTACK_DISABLE_GPU row now states the effect and the switch: set
GSTACK_DISABLE_GPU=off to enable WebGL (off is the only recognized value).
The reworded config-mismatch refusal links BROWSER.md's Daemon discipline
section. Free tests pin both.

Co-authored-by: Hagetino <Hagetino@users.noreply.github.com>

* test(windows): name the B2, B4, B5 and B12 tests in the curated Windows lane

The runtime-root bundle tests lived in browse/test/config.test.ts and the
Copilot glossary test in test/host-config.test.ts, both excluded from the
Windows lane, so the two Windows-only fixes were never checked on Windows.
Move each into its own Windows-safe file (unchanged assertions) and list it,
the piped-stdin chain test and the /cso docker.exe discovery test as named
Windows coverage in scripts/lib/windows-curation.ts.

* fix: the free-text block exits loudly when mktemp fails; spec and redact tests follow the file mechanism

The redact resolver test, the #2679 mktemp guard test, the /spec invariant
and the spec secret-sink harness pinned the heredoc transport. They now pin
the agent-written file: the harness runs the draft-file block, writes the
draft the way the file-write tool would, then runs the real scan fence, with
every HIGH/MEDIUM/scanner-failure assertion unchanged.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* test: hostile payloads through the generated posting blocks, identifier grammars, every quoting shape

- The lint reads a whole command, so an unquoted multi-word placeholder is
  caught; negative controls cover double, single and no quotes, quoted and
  unquoted heredocs, continuation lines, and quoted-only identifiers used
  unquoted.
- Every allowlisted grammar rejects quote-breaking characters (and shell
  operators and whitespace when used unquoted); git accepts 'fix;id' and
  'fix`id`' as branch names, the ref grammar does not.
- Greptile replies, test-failure issues (GitHub and GitLab) and /spec filing
  and archive run from the real generated text with stub gh/glab: a body
  with backticks, $(...), an apostrophe and lines equal to EOF and the old
  heredoc delimiters arrives byte for byte, nothing in it runs, and the temp
  file is gone. An unwritten file is refused with its path and the manual
  command, and nothing is sent.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* test(ship): the PR check precedes the Greptile dispatch, and each skip names its reason

Runs Step 10's generated check against stub gh for gh missing, not logged
in, a non-GitHub remote, no PR, a failed lookup and an existing PR. Fails on
the previous text (which printed PR: unknown and dispatched).

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* feat(ship): ship-measure runner for the measure-then-fix loop (A6)

scripts/ship-measure.ts owns /ship's diagnostic measurement of a red eval
case: per-kind aggregation (rule trials; behavior panels of 3, each at its
own 2 of 3 plus 90% of trials; judge outputs scored by 3-sample panels,
standalone judge ids accepted), one artifact directory per baseline,
repair round and trial, a diagnostic label with verdict null that refuses
the eval history dir, an estimated admission budget that reserves every
concurrent trial and reconciles actual cost, the ask-once rule, the
repair-round limit, the unmeasured skip and the per-case PR table.

Free-suite flakes (CEO-1): 'free' reruns one shard's exact file list N
times, rerun 1 alone as the baseline and the rest in parallel at
max(1, floor(cores / shard concurrency)), each with its own HOME, state
root and flake ledger, GSTACK_FREE_RETRY_FLAKY=0, under a wall cap that
reports a partial count. test:ubicloud --diagnostic runs the same on one
VM with the retry off.

bin/gstack-config gains the DX-7 keys with their CEO-9 defaults and
rejecting validators. Free tests drive every kind, the budget stop, the
unmeasured skip and the free reruns with fake runners.

* fix: a Codex usage limit is quota_exhausted, a plain 429 is rate_limited, and only Codex's own error lines count (#3051 rework)

The probe's quota signature used to treat any rate limit, "too many
requests" or 429 as quota, and read the whole output, which echoes the
prompt. Now only Codex's trailing ERROR: / stream error lines are read.
A usage-limit or insufficient_quota line is MODEL_QUOTA_EXHAUSTED (exit 4,
cached 15 minutes; quota wins over a 429 that names insufficient_quota); a
plain 429 is MODEL_PROBE_RATE_LIMITED (exit 0, state rate_limited, never
cached), reported as CODEX_MODE: unverified (rate_limited) so the review
still runs and decides.

The quota HINT says how many minutes gstack skips Codex and how to retry
now (GSTACK_CODEX_PROBE_RETRY=1 skips the negative cache for that probe, or
delete <state root>/.codex-model-probe), with the full troubleshooting URL.
classifyOutsideReview reads the same trailing lines: a mid-review quota is
unavailable (quota_exhausted), a mid-review 429 unavailable (rate_limited).
New gate row rate_limited and docs anchor codex-rate-limited; the quota row
documents the retry. Tests cover each line shape, the echoed prompt, exit
0, timeout, retry recovery, probe/classifier parity, and two fresh-shell
preflight blocks where the second makes no paid Codex call.

Co-authored-by: venkatareddyg <venkatareddyg@users.noreply.github.com>

* ci: the eval image installs zsh, and the macOS lane sources the Codex probe from zsh

Dockerfile.ci adds zsh so the sourced-helper guard runs (and fails loudly
when zsh is missing) inside the eval container too. The macOS named
regressions job sources bin/gstack-codex-probe from zsh in an unrelated
directory and runs _gstack_codex_select_model exec (no paid call), then runs
the sourced-helper guard on the macOS runner. A free test pins both lanes and
runs the macOS step's script locally.

* test: deterministic regression for the Codex watchdog freezing the command before reaping its children

The existing stubborn-child test caught the reap-then-kill race only some of
the time (2 of 3 runs on the pre-fix watchdog). A pkill that sleeps after
reaping widens the window between reaping the children and killing the
command, so a watchdog that does not SIGSTOP the command first always lets
it print "late" after the deadline. Fails 3 of 3 on the pre-fix watchdog,
passes 3 of 3 on the current one.

* test: live codex smokes run codex in a private HOME and CODEX_HOME

codex-resume-flag-semantics and codex-e2e-sol-scope probe the codex CLI (the
latter at module load, in every free run), and every codex run writes
$CODEX_HOME/tmp/arg0, so with a real codex on PATH they wrote into the
developer's ~/.codex. Both now give codex a private HOME and CODEX_HOME and
remove it afterwards. A free test runs each file against a stub codex that
records HOME and CODEX_HOME and asserts neither is the caller's.

* feat(ship): a red eval runs classify, measure, fix, re-measure, gate once (A6)

Step 6 no longer stops at the first red paid eval: it reads the new lazily
loaded measure section and runs the loop for each red case, and Step 5
measures a failed free-suite shard (diagnostic reruns, retry off) before
the triage can call it pre-existing. The section states the diagnostic-
not-verdict rule, the per-kind table, the ask-once and named-red stops,
the unmeasured label and the honest-fix wording (observed k/n after fix
at <cause>). A free test pins the steps and their order in the generated
skill and checks every command and flag it names exists in the runner.

Ship's parity cap rises to 1.455 (measured 272,944 / 187,706 = 1.4541).

* docs(testing): diagnostic measurement is not a verdict; per-kind measure table (A6)

* test(ship): seeded-flake paid case for the measure loop (A6, periodic, behavior)

A fixture repo with no remote documents a single-case eval command and a
gate command, both free local stubs. queue-priorities claims trial slots
with atomic mkdir (reset per ship-measure round) and fails slots 2, 5 and
9 while src/priorities.js leaves its seed-shuffled list unsorted, printing
failure_cause and failure_detail that name the line. The paid case gives
/ship's generated Step 6 and measure section to a live session and asserts
it measured the case alone, fixed the named line without touching the
eval, re-measured at least 10 passing trials on the sorted file, and only
then ran the gate once; any question other than the spend question is a
contract violation.

A free test measures the fixture through the real ship-measure CLI: 7/10
unsorted, 7/10 after an unrelated edit with the gate still red (ENG-13's
negative control), 10/10 after the sort with the gate green.

* test(cso): the missing-scanner case points Docker at a dead socket

'an optional scanner prerequisite does not turn completed manual coverage
partial' assumed no usable Docker. On a machine with Docker and a pulled
gitleaks image the scan completed and the test failed (also on main). The
scan step now gets a DOCKER_HOST that cannot connect, which is the
prerequisite-missing condition the test describes.

* fix: Aside prompts travel as agent-written files, never inside the command

The open-ended reading recipe (browse, devex-review), scrape's fuzzy-intent
read and the shared web-research block put the model's question, query and
reply format inside a double-quoted _aside_exec argument, where backticks and
$(...) from page or repo text would run. The agent now writes the prompt into
a private .gstack/tmp file; the send block rebuilds the path from the printed
basename, refuses when the file is empty, and passes "$(cat "$PROMPT_FILE")".
The fixed read-only sentence stays in the shell source.

aside-driver pins move from the inline prompt to the file-based send block.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix: design briefs and approval feedback travel as agent-written files

Design briefs went into $D variants/generate/check as a double-quoted
argument, and approval feedback was spliced into a single-quoted echo of JSON,
so a quote, backtick or $(...) in either broke the command or ran. The agent
now writes each brief into a private .gstack/tmp file and the block passes
--brief "$(cat "$BRIEF_FILE")" (the CLI's --brief-file expects a JSON
brief, so plain text uses --brief). variants and the later check reuse the
same brief file. approved.json is built with jq -n from --arg values and
--rawfile feedback, so the feedback is data. The office-hours mockup flow
creates its brief and feedback files in one block.

design-approval-flow now writes the feedback file (with quotes, backticks and
$(...) in it) instead of substituting <FEEDBACK>, and checks the file is
removed after the save.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix: carve office-hours on every host to stay under the 160,000-byte ceiling

With its brief, feedback and research prompts moved into files, the inlined
office-hours render is 161,329 bytes on Codex and 160,302 on Factory, over
SKILL_BYTE_CEILING, so gen-skill-docs --host all fails. Per the C4 policy
(carve every skill whose external render exceeds the ceiling), office-hours
joins ship and plan-ceo-review in CARVED_ON_EVERY_HOST. The Claude render is
unchanged; external hosts read its sections relative to the installed skill.
external-host-carve and qa-lazy-sections now include office-hours in their
carved-skill lists.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* chore: regenerate after the free-text mktemp guard

* fix(codex): prompts and review focus travel as agent-written files

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

Challenge and consult prompts, and the /codex review focus text, are written
by the agent into private .gstack/tmp files and fed to Codex on stdin or read
back with cat; no heredoc carries them. Consult's new and resumed runs share
one block (and one parser) so no placeholder stands in for the parser; a
resumed run reads its session id from .context/codex-session-id and checks it.
Adds the A3 group 2 placeholder allowlist block.

* fix(benchmark-models): an inline prompt goes through a prompt file

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix(land-and-deploy): PR head repository and branch travel as data

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

The branch check reads headRepository/headRefName from gh's JSON into quoted
shell variables; ls-remote, the confirm-first delete and the local delete use
those variables instead of model-substituted names. The postfail test now pins
the variable form it replaces.

* fix(office-hours): the reviewer receipt must match the receipt grammar before the check runs

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix(setup-gbrain): cleanup-orphans collects the PAT with read_secret_to_env

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix(ship): PR body drafts and rejected test paths are agent-written files

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

The PR body is written into two private drafts (through ## Documentation, and
from ## Test plan on); the scan block splices Step 14.5's section between them
into the scanned temp file and deletes the drafts. Rejected test paths are read
from a written list, and paths outside the repository are skipped. Tests that
ran the old heredoc blocks now write the drafts and list first.

* fix(sync-gbrain): orchestrator flags must match an allowlisted grammar

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix: the gbrain page save and external-host placeholders pass the free-text rule

The gbrain save block passed the whole page through a quoted heredoc inside
--content; the agent now writes it to a mktemp file and gbrain put reads it
on stdin. The Claude Code outside-voice runtime root and session mode, and
the feature slug, join the allowlist with grammars; unused entries are
dropped, and a test keeps every allowlisted placeholder tied to a real use.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix: skill-specific Aside research queries travel as prompt files

investigate, office-hours, plan-ceo-review, review, plan-eng-review,
design-consultation Phase 2, the shared test bootstrap (B2) and QA's test
bootstrap each carried their own _aside_exec call with the model-filled query
inside a double-quoted argument. Skills that already carry the Web research
block now give only the prompt-file text and use that block to create, write
and send it; design-consultation, the shared bootstrap and QA create the file
and send it with the new {{ASIDE_RESEARCH_SEND}} resolver, which is the same
send block the research section uses (read-only sentence stays in the shell).

aside-driver's B2 pin and qa-browser-preservation's bootstrap contract move
from the inline call to the prompt file and the shared send resolver.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* fix: prose design commands pass briefs and feedback as data

The DESIGN_SETUP command list taught `iterate --feedback "..."`, and
design-consultation's late-change and plan-design-review's slop-regenerate
lines showed `--feedback "<feedback>"` / `"..."`; design-shotgun's retry
showed `generate --brief "<brief>"` and `evolve --brief "<brief>"`. Feedback
now reads "$(cat "$FEEDBACK_FILE")" from an agent-written mktemp file
(the setup note says how), and the shotgun retry reads the variant's own brief
out of briefs.json with jq instead of retyping it.

Co-authored-by: dgrant <dgrant@users.noreply.github.com>

* test: record parity sizes after the free-text file mechanism; refresh ship goldens

Measured union and skeleton sizes for ship, plan-eng-review,
plan-design-review, office-hours, design-consultation, review, qa, browse
and design-shotgun after every posting, prompt and brief site moved to
agent-written mktemp files; the ship goldens pick up the question-tuning
slug and the dropped free_text field.

* test: parity caps for the combined wave head (rounded up past the measured ratio)

* fix(plan-ceo-review): the mode handoff helper reminds the model to lead its chat with the line (#3038)

Periodic run 37272185151's auto-decide-preserved red replays as the Step 0E
handoff-line class: the model ran gstack-ceo-mode-handoff --auto, got the
exact line, then opened its chat with a heading and a paraphrase, so the
user never saw the auto-decide line (the terminal collapses tool output)
and the detector saw no outcome. The helper now prints a one-line reminder
on stderr beside the line; the detector accepts the line followed by that
reminder and nothing else. The ship Greptile check test reads its template
(test-of-test ratchet).

* test: every troubleshooting link in shipped code resolves, relative or full URL

* docs(todos): Oct 6 fix-wave follow-ups

* v1.91.32.0 release notes: Codex works from zsh, free text never enters shell commands, honest failures

* fix(plan-ceo-review): show the mode handoff line through a PostToolUse hook, not model compliance (#3038)

The stderr reminder beside the handoff line measured 1/10 on
auto-decide-preserved: the model still opened its chat with a paraphrase
('I've set review mode to HOLD SCOPE...'), so the user never saw the line and
the detector saw no outcome. /plan-ceo-review now declares a Claude-only
PostToolUse hook that shows the helper's exact line as a system message.
Diagnostic measurement on this head: 10/10, the line visible in every trial
while no trial's chat led with it. The reminder is reverted.

* fix: type the handoff hook's line filter

* fix: repairs from the integrated free suite

- The self-locate markers looked like heredocs to the pipe-deadlock guard;
  they are plain comment fences now.
- The spawned block keeps the canonical 'conservative non-destructive'
  phrase the cross-surface drift guard pins.
- The free-text block's mktemp refusal says 'mktemp failed', as every
  /codex temp file must.
- The batch transcript read reports sizes in MiB.
- docs/TESTING_INTERNALS.md gets a real 'Ship measure' heading for its
  anchor.
- The early-question harness admits /plan-ceo-review's handoff hook by its
  exact command (it only adds a system message); any other PostToolUse Bash
  hook is still refused.
- Paid file count and the unconsumed-file example follow the new seeded
  case; ship goldens refreshed.
- The CSO scanner test points Docker at a dead socket, so a machine with the
  pulled image does not scan.

* test(cso): a host with no Docker binary is also a no-scan host

The missing-image scan test failed on a Docker-less VM (docker is not
installed in a trusted directory). Every accepted message is a reason the
scan cannot run here; the not_assessed, helper-owned artifact assertions are
unchanged.

* ci: split the USERPROFILE assignment from export (SC2155)

* fix(browse): Windows runtime roots read .source-path as a native path (#3026)

The new windows-setup-e2e journey showed the installed browse could not
start its server. setup runs under Git Bash and wrote .source-path as an
MSYS path (/d/a/gstack); Node read it as \d\a\gstack on the current drive,
found nothing, and fell back to the root's own bundle, which cannot import
playwright. setup now writes cygpath -m, and the CLI maps an MSYS drive path
for installs written by an older setup. The journey prints the daemon and
startup logs when the chain fails.

* fix(ship-measure): defects from the parallel #3034 plan review

- A trial passes only with a JUnit testcase that executed and passed; a
  --case that selected nothing also exits 0.
- measure runs a no-cost --list preflight that must plan the case's own
  file before any paid trial.
- Diagnostic trials drop EVALS_CACHE_DIR/EVALS_CACHE_RUNTIME_ID, so they
  never reuse a stored judge pass or publish one the gate could reuse.
- Gate once: when CI runs the gate on push, the push is the gate run; the
  skill no longer also starts eval:bg:pr (a second paid gate).
- ubi-runner: down fails unless the VM is confirmed gone, and nothing
  sweeps other runners' VMs by default (UBI_GC_HOURS opt-in).

* test(windows): the seeded-flake fixture drives a POSIX shell stub, so it stays off the Windows lane

CreateProcess cannot exec the fixture's ./evals.sh shebang script (status
null on windows-free-shard 6). The measure runner is covered on Windows by
ship-measure.test.ts, which uses in-process fake runners.

* fix: the free-text block refuses an unset temp dir instead of writing at /

The rendered block ran mktemp "$_GT/..." with _GT unset in the portability
test; on Linux that fails at /, but Git Bash on Windows can write to its
install root, so the block went on (windows-free-shard 3). It now uses
${_GT:?}, so an unset directory always fails closed with the mktemp
message.

* test: pin the guarded ${_GT:?} temp-file form

* test: the Greptile check's gh stub uses a portable shebang

On Windows the absolute bash path has a space, so the stub never ran and
every case read 'gh not logged in' (windows-free-shard 1).

* test: the Greptile check runs with the native PATH and the gh stub first

The PATH-only stub bin could not run its stub on Windows (every case read
'gh not logged in'). Stub cases now keep the real PATH with the stub first;
only the no-gh case drops directories holding a real gh.

---------

Co-authored-by: marcosdetry <marcosdetry@users.noreply.github.com>
Co-authored-by: akshaykalia <akshaykalia@users.noreply.github.com>
Co-authored-by: scottgigante-hubflow <scottgigante-hubflow@users.noreply.github.com>
Co-authored-by: woodsonl <woodsonl@users.noreply.github.com>
Co-authored-by: bezdekt <bezdekt@users.noreply.github.com>
Co-authored-by: dgrant <dgrant@users.noreply.github.com>
Co-authored-by: venkatareddyg <venkatareddyg@users.noreply.github.com>
Co-authored-by: CharlesBerg85 <CharlesBerg85@users.noreply.github.com>
Co-authored-by: VaibDeveloper <VaibDeveloper@users.noreply.github.com>
Co-authored-by: Hagetino <Hagetino@users.noreply.github.com>
Co-authored-by: Rekin226 <Rekin226@users.noreply.github.com>
Co-authored-by: ekson73 <ekson73@users.noreply.github.com>
Co-authored-by: tianxchen <tianxchen@users.noreply.github.com>
Co-authored-by: BenjaminDSmithy <BenjaminDSmithy@users.noreply.github.com>
Co-authored-by: ubay887 <ubay887@users.noreply.github.com>
Co-authored-by: nareshh74 <nareshh74@users.noreply.github.com>
Co-authored-by: syong <syong@users.noreply.github.com>
2026-10-06 23:42:19 +00:00

229 KiB
Raw Permalink Blame History

gstack fix wave (October 6, 2026): Codex works on macOS again, no shell injection, honest failures

Implementation plan

Context

Base: origin/main 55f8151 (v1.91.30.0): PR #3033 (Bun 1.4.2, honest eval reports) has merged, and the branch merged it at 9207fab. While this review ran, the branch gained A1 (f0531a2), A3 (a853e41), A2 (597e483), a Codex watchdog fix (2e1d3e7), A4 (ffde420) and A5 (e77c532); CEO-18 under "Accepted review amendments" lists the rework each still owes. #3033 also edits browse/src/cli.ts (B2, B10), lib/cso/process.ts (B5), lib/redact-patterns.ts (B3), .github/docker/Dockerfile.ci and .github/workflows/free-tests.yml (A1's zsh install), docs/troubleshooting.md (A2's anchor), docs/TESTING_INTERNALS.md (A6), review/SKILL.md.tmpl, TODOS.md and the release files, so those hunks are reconciled at that merge.

Triage on 2026-10-06 covered every issue and PR opened since the October 3 wave triage (#3018 to #3052, plus #3012 and #3016). Every claim below was checked against main with a probe or a code read; contributor code was read as evidence only. Nothing is merged from a contributor branch. Each fix is rewritten here, with Co-authored-by credit for the contributors whose diagnosis or design it uses.

The severe items share two patterns:

  1. Shell assumptions that break real users. Sourced helpers assume bash (macOS default is zsh, and Claude Code's Bash tool runs it), and reply templates put untrusted text inside double quotes, where backticks run as commands.
  2. Failures reported as the wrong thing. A Codex usage limit reads as "inconclusive; proceeding", an oversize autoplan journal reads as a retryable identity failure, a shell-built autoplan init reads as "init missing", and a Windows browse chain with piped input exits 0 having run nothing.

Release promises (the finish line)

  • P1. Codex outside voice works from zsh. Every sourced gstack helper locates itself correctly when sourced by bash or zsh, from any working directory. A free test sources each one under both shells, and CI installs zsh so the test cannot skip there.
  • P2. Untrusted text never runs as shell. No generated skill passes reviewer, diff or error text to a command inside double quotes. A lint fails on the pattern.
  • P3. Every failure names its real cause and its real fix. Codex quota, oversize journals, shell-built autoplan inits, Windows chain input, Windows Docker and Windows browse dependencies each get a specific message with a fix line, documented in docs/troubleshooting.md or docs/autoplan-guard-troubleshooting.md.
  • P4. A red paid eval gets measured and fixed, not rerun blindly. /ship runs the measure-then-fix loop from #3034 (approved 2026-10-05), and the weekly periodic red (#3038) is fixed with that loop.

Tier 1: must ship

A1. #3024: sourced helpers break under zsh (PRs #3027, #3049, #3052). Probe on main: zsh -c 'source bin/gstack-codex-probe && _gstack_codex_select_model exec' prints Module not found "/../scripts/resolve-codex-generation-model.ts" and CODEX_MODEL: invalid, so since v1.91.16.0 every Codex outside pass on macOS zsh reports model_unusable. bin/gstack-egress-lib.sh resolves its directory to the caller's cwd under zsh (probe: prints /tmp), so receipts and the state root are looked up in the user's project.

  • Fix: one self-locate idiom, resolved once at source time: BASH_SOURCE[0] under bash, ${(%):-%x} under zsh (inside eval so bash never parses it), else the default install path. Apply it to bin/gstack-codex-probe (5 sites) and bin/gstack-egress-lib.sh. bin/gstack-paths is executed, never sourced, so it keeps BASH_SOURCE under its bash shebang and is outside the sourced-helper guard. Keep the probe's "no command execution at source time" rule (parameter expansion only).
  • Rejected alternatives: $0 (#3049) is the function name when read inside a function and zsh under zsh -c; functions_source (#3027, #3052) is rejected for simplicity (CEO-28), not version support.
  • Class guard: a free test enumerates every file the generated skills source or . (derived from the rendered SKILL.md and section files, not a hand list), sources each under bash and zsh from an unrelated cwd, and asserts the resolved directory and one real call per helper (model resolver, state root, egress home). A lint fails on BASH_SOURCE in a sourced helper outside the shared idiom. CI installs zsh in the free lane and the Docker image; under CI=true a missing zsh fails the test instead of skipping it.
  • Credit: @marcosdetry (report and diagnosis), @akshaykalia (egress-lib finding), @scottgigante-hubflow, @woodsonl, @bezdekt.

A2. #3051: Codex usage limit reported as "inconclusive; proceeding". Code read: _gstack_codex_model_probe matches only model and broken-install patterns, so a quota error falls through to MODEL_PROBE_INCONCLUSIVE, is never cached, re-probes for 30 s in every skill, and drops Codex's own message with the reset time.

  • Fix: a new probe outcome quota_exhausted (exit 4), matched on Codex's usage-limit and insufficient_quota lines on a failed, non-timeout call (a plain rate-limit or HTTP 429 line is the separate, uncached rate_limited outcome in CEO-14). It prints Codex's error line verbatim, maps to CODEX_MODE: quota_exhausted, and every caller treats it as unavailable missing coverage (never a pass) and skips the remaining Codex calls in that run. Cached for 15 minutes under the same signature as MODEL_UNUSABLE.
  • Add reason codes quota_exhausted and rate_limited to lib/gate-outcomes.ts, each with its docs/troubleshooting.md anchor; classifyOutsideReview recognizes the same lines in a review's stderr so a mid-run quota error reads as unavailable (quota_exhausted) and a mid-run 429 as unavailable (rate_limited).
  • Tests: probe with a stub codex that prints the real message (from the issue) and exits 1; a stub that prints it and exits 0 must stay MODEL_OK; timeout keeps its inconclusive contract; every generated preflight block branches on quota_exhausted.

A3. #3046: reply templates run backticks as shell. Code read: review/greptile-triage.md:102,108 post -f body="<reply text>", and the templates below put `<commit-sha>` and quoted diff code in the body. In double quotes bash runs each backtick span. scripts/resolvers/preamble/generate-test-failure-triage.ts:74-75 does the same with raw test error output in --body "...".

  • Fix: both templates put the body in a file the agent writes with the host's file-write tool at a mktemp path, and pass -F body=@file / --body-file; no heredoc carries untrusted text (CEO-12). Audit every generated skill for the same shape (untrusted placeholder inside a double-quoted command argument) and fix each.
  • Class guard: extend the generated-bash lint as specified in CEO-12 (every command, every quoting shape, reviewed placeholder allowlist).
  • Credit: @dgrant.

A4. #3050: oversize autoplan journal reported as a retryable identity failure. Code read: lib/claude-public-transcript.ts:555 throws OwnedReadError('identity') when the journal exceeds 32 MiB; the hook treats identity as deny-and-retry, and a journal only grows, so /autoplan can never enter Phase 1 in a long session (reported journals: 52, 58 and 73 MiB).

  • Fix: a distinct too_large reason, classified as a hard cause with the supported fallback ("This session's journal is 52 MiB, over the MiB limit. Start a new session for /autoplan, or run /plan-ceo-review, /plan-eng-review and the other reviews individually.", with the real size and the cap in force). New row in docs/autoplan-guard-troubleshooting.md. The cap, both oversize paths and the follow-up are specified in CEO-2 under "Accepted review amendments" below.
  • Tests: an oversize journal yields too_large, never identity; a real identity mismatch still yields identity.

A5. #3040: weekly OSV scan red. From run 37280512718: fast-uri 3.1.6 (two High, fixed 3.1.8), ip-address 10.3.1 (pinned by our own override; fixed 10.7.1; reachable through socks in browse's proxy path), hono 4.13.5 (fixed 4.13.7), dompurify 3.4.14 in lib/diagram-render (fixed 3.4.16), braces 3.0.3 in lib/diagram-render (High, no fixed release).

  • Fix: raise the overrides to the fixed versions, rebuild the diagram-render bundle, and run the browse proxy tests. braces gets an [[IgnoredVulns]] entry with ignoreUntil only after confirming it is build-time only and never in the shipped bundle; otherwise replace the path that pulls it.

A6. #3034: /ship measures a red paid case before rerunning the gate (approved). As specified in #3034: classify the red line, run that case alone N times (default 10, parallel) on the current head, fix at the cause until it passes at least 90%, then run the full gate once; report a per-case table in the PR body. Diagnostic trials never change a recorded verdict and are labeled diagnostic, never uploaded as verdicts (the EVAL_POLICY distinction goes in docs/TESTING_INTERNALS.md). Behavior cases measure by panels of 3 and judge cases by 3-sample panels. Pre-registered N, a per-case spend cap shown before running, and a repair-round limit that stops with a named-red report. Generic over the project's documented eval command, with gstack's as the default. Free test pins the steps and the "diagnostic, not verdict" rule; one paid case uses a seeded flaky fixture.

A7. #3038: weekly periodic red (auto-decide-preserved, outcome=timeout) and #2996 (marathon red). After #3033 lands, measure auto-decide-preserved 10 times alone on main with A6's loop, root-cause the timeout, and fix it to at least 90% without raising budgets. Confirm the marathon lane is green on main after #3033 and close #2996 with the run link, or fix what is still red.

Tier 2: ship if green

  • B1. PR #3045: shell-built autoplan init gets the generic "init missing" denial. When no init binds but a Bash call looks like a snapshot init through variables, substitution, chaining or pipes, the denial names that as the cause and prints the literal re-run. The binding rule does not change. Credit @CharlesBerg85.
  • B2. PR #3039: Windows browse chain with piped input exits 0 having run nothing. Read stdin synchronously (fs.readFileSync(0)) only when stdin is not a TTY; empty input gets the existing usage error and exit 1. Test pipes a flow into the real CLI against a stub daemon. Credit @VaibDeveloper.
  • B3. PR #3048: redaction false positives on TypeScript parameter types and JSX expressions. Rewrite as one rule instead of two keyword heuristics: an env.kv value that is a bare identifier reference or a dotted property chain (optionally joined by +) is source syntax, not a literal. True-positive controls must still flag: a YAML bare high-entropy token, a quoted literal, a brace-wrapped literal, and a literal concatenated to a property read. Credit @tianxchen.
  • B4. #3026: Windows runtime roots cannot start the browse server. Defect 1 (module-scope server.ts resolution) was fixed in v1.91.19.0. Defect 2 remains: server-node.mjs imports playwright and the other externals, and the Windows runtime roots (copies, not symlinks) have no reachable node_modules. Fix in setup for every runtime-root host: make the source checkout's node_modules reachable from the runtime root on Windows (a directory junction; no admin rights needed), and run setup's "Node can load Playwright" check against the installed runtime root, not the checkout. Verified by the Windows CI lane; not testable live here.
  • B5. #3028: /cso on Windows never finds Docker. Bind docker.exe at setup the same way git is bound (GSTACK_CSO_TRUSTED_DOCKER), accept it only from a directory a standard user cannot write, and put its directory in the child TRUSTED_PATH so credential helpers resolve. Coordinate with the CSO thread (GSTA-24). Investigate the side note (a Java repo detected as Python) and fix it if it reproduces.
  • B6. #3018: root router sends requests to skills the user disabled. Main already drops skills disabled through gstack's own disabled_skills config from the rendered router (C8, v1.91.19.0: omitDisabledSkills in scripts/gen-skill-docs.ts). The remaining gap is skills turned off in Claude Code's skillOverrides, which a render cannot see, plus the wording. Route only to skills present in the session's skill list, say so in the router, and replace "When in doubt, invoke the skill" with when to invoke and when not to. Routing evals must stay green.
  • B7. PR #3016 (residual): spawned sessions and consent. Main already withholds the artifacts-sync consent prompt from spawned and headless sessions. Remaining gap: the spawned auto-choose rule's exception list covers destructive options but not consent or publishing to a remote. Add "granting consent or publishing data off this machine" to that exception in the skill-start spawned block and the hook directives. A consent the user already gave interactively (a saved artifacts_sync_mode) stays in force; the PR's change to stop syncing in that case is not taken.
  • B8. PR #3032 (lean version): a hook that does not parse blocks every tool call. Claude Code runs gstack's hook shims through /bin/sh; a shim with a syntax error exits 2, which blocks the tool call in every session. Before registering hooks, setup runs bash -n on each shim and a parse check on its TypeScript entry, and refuses to register with the file and line. About 50 lines plus a test, instead of the PR's 4,600.
  • B9. #3020: /ship dispatches Greptile triage when no PR exists. Check for a PR inline first; with none, skip the dispatch and record "Greptile: runs on the PR once it exists". Running Greptile in parallel during /ship (push early) is a larger change and goes to TODOS.md.
  • B10. #3030: browse clarity. BROWSER.md notes that the default GPU flags disable WebGL and GSTACK_DISABLE_GPU=off restores it; the config-mismatch message says another session started the daemon with --headed/--proxy and offers both remedies.
  • B11. PR #3029: /sync-gbrain offers a --dream build for a source with no code pages. gstack's own sync always uses --strategy code, so the PR's soft-delete claim does not apply to gstack's path. The misleading offer is real for sources added outside gstack. Probe code scope (code-def on an impossible symbol, read .status) before offering, if the probe is confirmed against a live gbrain at or above 0.60; otherwise move to TODOS.md.
  • B12. PR #3047 (minimal): Copilot on Windows runs bash blocks in PowerShell. Add one line to the Copilot tool glossary: on Windows, run each bash block through Git for Windows' bash.exe, never PowerShell or WSL bash. The PowerShell repair skill and README section are not taken.

Not in this wave

  • #3037 (Windows Bun aborts): fixed by #3033's Bun 1.4.2 upgrade; close when it merges.
  • #3012 (question tools only for unanswered questions): main's tool-resolution and failure fallback already cover the reported stop; the all-skill preamble churn is not worth the eval risk. Close with thanks.
  • #3021 (context-bill benefit estimates): a feature, not a fix; stays open for a later feature pass.
  • Dependabot #2923, #2988, #3043: routine bumps outside a severe wave. #3043's agent-sdk jump (0.2.117 to 0.3.288) is a deliberate harness bump and needs its own PR with the Claude Code pin.
  • Older open feature requests and issues triaged in the October 3 wave.

Tracker hygiene

  • On merge, close each fixed issue with the release, the upgrade command for the reporter's host, and an invitation to reopen with ./setup --status output.
  • Close every adopted PR at merge with a link to the wave PR and a thank-you; close #3012 and #3016 with the reasons above; close #3027, #3049 and #3052 as superseded by A1 with credit.

Validation

  • Focused free tests per item, written to fail on main first. Run the free suite once at the end on Ubicloud.
  • Paid: A6's seeded-flake case, A7's measurement, routing evals for B6, and the PR gate. Diagnostic trials follow A6's rules.
  • Windows items (B2, B4, B5, B12) are verified by the Windows CI lane to the extent CEO-24 states; the PR names each Windows behavior that was not exercised.
  • One PR, separate commits per item, patch version bump.

Accepted review amendments

Each amendment below was accepted by the /autoplan review. Where an amendment names an item, it adds to or overrides that item's text above.

  • CEO-1 (A6, free-suite flakes). As #3034 specifies, when a free-suite shard fails during /ship, /ship reruns that shard's exact file list (which also catches order-dependent failures) N times (default 10), in parallel, under a 10-minute wall-clock cap, with the runner's own flaky-retry turned off so retries never inflate the measured rate. This needs an executable runner change, not just workflow wording: scripts/ubicloud/test-free.sh sets GSTACK_FREE_RETRY_FLAKY=1 unconditionally today, and a free test proves the diagnostic launcher disables retries and records every outcome. Each rerun gets its own state (temp HOME, temp state root and its own port range), and the first rerun runs at the shard's original concurrency as the baseline; parallel reruns are labeled as such so contention they add is not read as the original failure. It runs the reruns on one Ubicloud VM, as N parallel processes, when UBICLOUD_API_KEY is set and the project documents a Ubicloud test command (gstack: bun run test:ubicloud); otherwise locally. The rerun uses the project's documented test command with the shard's file list as arguments; when the runner cannot take a file list, it reruns the whole failing command. It reports the pass rate and a failure capture before calling the failure pre-existing or pushing; when the cap hits, it reports the partial count (for example 6 of 10 completed). These reruns are labeled diagnostic and never change the recorded verdict. The free test that pins A6's steps also pins this step in the generated ship skill.
  • CEO-2 (A4 cap and follow-up). This wave keeps the owned-journal cap at 32 MiB (OWNED_TRANSCRIPT_MAX_BYTES, already in the working tree) and does not raise it on timing evidence: both outside reviews showed a larger cap only moves the same cliff. The summed multi-file batch read (lib/claude-public-transcript.ts around line 341) keeps its own 32 MiB constant and stops throwing a plain Error with a hard-coded "32 MiB": it reports too_large with the real size and cap, and so does the ownedParentTranscript-narrowed loop. Free boundary tests inject a small cap through a test seam and cover the owned read, the snapshot path and the narrowed loop: at the cap reads; one byte over yields too_large, never identity; a real identity mismatch still yields identity. No multi-MiB fixture enters the free suite. A P1 TODOS.md entry, targeted at the next wave, specifies a bounded or incremental owned-journal read that keeps every ownership and ancestry check; its acceptance is an /autoplan run that completes in a session whose journal is over 100 MiB, with phase-entry latency and peak memory measured on macOS and Linux developer machines.
  • CEO-3 (A6 paid case shape). The seeded-flake paid case covers only the measure, fix, re-measure and gate segment of /ship. Its fixture project has no remote; its CLAUDE.md documents a single-case command (./evals.sh case <id>) and a gate command (./evals.sh gate), both local stubs. The fixture's inner case is kind rule, so CEO-9's rule budget applies (10 trials, at least 9 passing). Each stub invocation claims the lowest free trial slot by creating slots/<n> with mkdir, which is atomic, so parallel trials never race; the trial in slot 2, 5 or 9 fails before the fix (3 of 10). Its failure output prints a failure_cause and failure_detail naming the nondeterministic line in one fixture file (it reads an unsorted list that the seed shuffles and compares it to a sorted expectation); the deterministic fix is to sort the list, after which it passes 10 of 10. The fixture's free test command passes instantly and its tree is clean, so /ship reaches the eval step at once; the stub's first gate run is red on the flaky case, and the case stops once /ship has run the gate command after the fix; it never reaches version, push or PR steps. Declared fixture answers: approve the CEO-9 spend question; any other question fails the case as a contract violation. Inner trials cost nothing and finish in seconds. The case is kind behavior (a panel of 3 independent trials, PASS at 2 or more with no contract violation), runs in the periodic tier, and asserts that /ship measured the case alone, fixed the named line, re-measured at or above 90%, and only then ran the gate command once.
  • CEO-4 (A7 replay first). A7 starts by replaying the captured transcript and public events of periodic run 37272185151 (auto-decide-preserved, outcome=timeout) against the known Step 0E handoff-line class (fixed in v1.91.19.0 by gstack-ceo-mode-handoff), as a free regression, before any paid trial. If the replay matches that class, it is a regression of that fix: fix it at the cause and keep the replay as a free regression test. If it does not match, the replay becomes the failure capture that starts A6's loop. The baseline measurement (10 trials alone, after #3033 lands) runs on main; the closing measurement runs on the wave head and needs at least 9 of 10 before #3038 is closed. If 3 repair rounds do not reach 9 of 10, A7 alone stops: the PR body carries the named-red report with every trial, #3038 stays open with that evidence, a P1 TODOS.md entry is filed, and the rest of the wave continues. This does not make any PR check red, because the case runs in the periodic lane.
  • CEO-5 (B6 pinned prose). The tests that pin "When in doubt, invoke the skill" (test/gen-skill-docs.test.ts around line 2117 and test/helpers/autoplan-preconfigured-fixture.ts line 7) are updated in the same commit as the wording, to assert the new when-to-invoke and when-not-to-invoke text. The TODOS.md "C8 router wording behind a routing panel" entry is closed with the routing-eval run link.
  • CEO-6 (TODOS.md writes). The implementation PR adds TODOS.md entries, in the repo's TODOS format, for: B9's full version (push early and run Greptile in parallel during /ship, #3020); B11 if the live gbrain probe cannot be confirmed; the P1 bounded or incremental owned-journal read (CEO-2); B5's side note if it does not reproduce (see CEO-10); the P1 entry for A7 if it stops red (CEO-4); every Tier 2 item reverted under CEO-11; the executed-subcommand probe (CEO-28); and a gstack helper that posts PR and issue text by passing arguments as an array, never through a shell string (the stronger form of CEO-12, deferred).
  • CEO-7 (P3 doc anchors). Every new failure state in this wave (a new refusal, denial or error, not a reworded informational line) has a doc anchor and a fix line: A2 quota_exhausted and rate_limited, B2, B4, B5 and B8's setup refusal in docs/troubleshooting.md; A4 too_large and B1's shell-built-init denial in docs/autoplan-guard-troubleshooting.md. B10's reworded config-mismatch message points to BROWSER.md's existing "Daemon discipline" section. Each message carries its anchor literally (docs/troubleshooting.md#<slug> or docs/autoplan-guard-troubleshooting.md#<slug>), and a free test enumerates every such reference in the source tree and checks the anchor exists (extending test/troubleshooting-anchors.test.ts).
  • CEO-8 (Windows coverage is named). Each Windows item names its free test and adds it to the curated Windows-safe subset (scripts/lib/windows-curation.ts): B2 the piped-stdin browse chain test against a stub daemon; B4 a setup test that the runtime root resolves playwright through the junction and that the Node load check runs against the runtime root; B5 a binding test for GSTACK_CSO_TRUSTED_DOCKER (accepted only from a non-user-writable directory, present in TRUSTED_PATH); B12 a generated-text test for the Copilot glossary line.
  • CEO-9 (A6 measurement defaults). The pass target is counted in trials of the case's kind: rule cases run 10 trials and need at least 9 passes; behavior cases run 4 panels of 3 (12 trials) and need every panel to pass and at least 11 of 12 trials; judge cases run 10 outputs, each scored by its 3-sample panel, and need at least 9 passing outputs. Before running, /ship always shows the estimate (per-trial cost from pass-rate history times trials). As #3034 specifies, it asks first when a case's per-trial cost is over a set amount (default $2 per trial) and, when the case has no pass-rate history, it shows "estimate unknown" and asks once. The repair-round limit is 3; after it, /ship stops with a named-red report. The eval command comes from the same discovery /ship already uses for test commands (CLAUDE.md/AGENTS.md, then package scripts); gstack's scripts/test-paid-shards.ts --tier <tier> --case <id> is the default; when no single-case command is documented, /ship asks once and records the answer in its report.
  • CEO-10 (B5 side note, bounded). The "Java repo detected as Python" side note becomes one fixture test: a Maven repository (pom.xml, Java sources) that also contains helper Python scripts must be detected as Java. Fix detection precedence only if that fixture misdetects; otherwise record the report in TODOS.md (CEO-6). Coordinating with the CSO thread (GSTA-24) is a process note, not a deliverable.
  • CEO-11 (Tier 2 "ship if green"). A Tier 2 item ships when its focused tests and every required CI check pass on the wave head. A Tier 2 item that stays red after diagnosis within the wave is reverted as its own commit and moved to TODOS.md with the failure evidence, and the PR continues. A red Tier 1 item holds the whole PR, with one exception: A7 may stop red under CEO-4, because its case runs in the periodic lane and no PR check depends on it. The PR is never called ready while any check is red.
  • CEO-12 (A3: free text never enters a shell command). The rule is structural: in a generated bash block, a free-text placeholder (bodies, titles, messages, error output, reviewer or diff text) may appear only as the contents of a file whose path came from mktemp; the command gets --body-file, -F body=@file or --title "$(cat "$file")". A bash block creates the private path with mktemp, the agent writes the text to it with the host's file-write tool, and the next block uses it. There is no heredoc fallback: if a host cannot write the file, the post is left unsent and the skill says so. Identifier placeholders that do enter commands (branch names, paths, PR numbers, comment ids) are on a reviewed allowlist, each with a validation grammar the skill applies before use (for example a branch name matching ^[A-Za-z0-9._/-]+$); a value that fails its grammar is not used. The lint fails on any non-allowlisted placeholder in a command argument of any command, in any quoting (double, single, or an unquoted heredoc), and reads \\ continuation lines as one command. Hostile-payload tests run through the real generated text: a body containing backticks, $(...), an apostrophe and a line equal to a common heredoc delimiter, and a branch name containing ;. The same commit fixes the literal \\n sequences and triple backticks inside the current test-failure templates (ship/sections/tests.md, generated from scripts/resolvers/preamble/generate-test-failure-triage.ts), which post broken issue bodies today even without untrusted input.
  • CEO-13 (shared-prompt edits keep eval coverage). B7 changes only the runtime skill-start spawned block and the hook directives (hosts/claude/hooks/spawned-directive.ts, hosts/claude/hooks/auq-error-fallback-hook.ts); it does not edit the static all-skill preamble (scripts/resolvers/preamble/generate-ask-user-format.ts), which keeps it consistent with the #3012 decision. A2's preflight change renders into every skill with an outside voice; its coverage is the free generated-preflight tests plus the outside-voice paid cases already in the PR gate (test/skill-e2e-outside-voice.test.ts, test/skill-e2e-outside-plan-disabled.test.ts). Validation adds AGENTS.md's cheap gates before any paid run: bun run gen:skill-docs freshness (all hosts), bun test test/parity-suite.test.ts, prompt-size and parity limits, the credential scan, bun run typecheck, bun run typecheck:test, test/module-size-ratchet.test.ts and test/spawnsync-timeout-tripwire.test.ts.
  • CEO-14 (A2 quota versus rate limit). Only Codex's usage-limit line ("You've hit your usage limit", including its reset time) and insufficient_quota map to quota_exhausted (exit 4, cached 15 minutes). A plain HTTP 429 or rate-limit line maps to a separate rate_limited outcome (exit 0, like the inconclusive contract, with _GSTACK_CODEX_PROBE_STATE=rate_limited): never cached, printed verbatim, reported as CODEX_MODE: unverified (rate limited), and the review's own result still decides. Stub tests: usage-limit line with exit 1 gives quota_exhausted and is cached; insufficient_quota gives quota_exhausted; a bare 429 gives rate_limited and is not cached; any of these lines with exit 0 stays MODEL_OK; a timeout keeps the inconclusive contract.
  • CEO-15 (A1 nested sources). A1's class guard enumerates sourced files transitively: every file the rendered skills source, plus every file those helpers source in turn (for example bin/gstack-state-root.sh, sourced by bin/gstack-codex-probe). Under both shells it makes one real call each to the model resolver, the auth probe, the state root and the egress home. The lint allows BASH_SOURCE only inside the one shared self-locate block, matched byte for byte across the helpers, and fails on other bash-only constructs in sourced helpers (${!name} indirection, declare -n, mapfile/readarray, [[ ... =~ ... ]] with BASH_REMATCH).
  • CEO-16 (B6 both texts). B6 changes both places that say "When in doubt, invoke the skill": the root router (SKILL.md.tmpl) and the CLAUDE.md routing-injection text that bin/gstack-skill-start (around line 498) offers to new projects, plus gstack's own CLAUDE.md routing section. gstack never rewrites a user's existing committed CLAUDE.md; the release note shows the new wording for users who want to update theirs.
  • CEO-17 (Tier 2 mechanics named). B1 detects a shell-built init when a Bash call names the snapshot tool or its init argument through any of: a variable holding the tool path, $(...) or backticks around the tool or its arguments, &&/;/|| chains, a pipe, or a redirect; negative controls keep the generic denial for bun "<SNAPSHOT_TOOL>" scope ... and for an unrelated bun other.ts init. B2: with no arguments, chain reads stdin only when stdin is not a TTY; a TTY with no arguments, or empty piped input, gets the existing usage error and exit 1 instead of waiting. B4: setup creates the junction from Git Bash with cmd //c mklink /J, replaces a stale junction, leaves an existing real node_modules directory untouched and says so. B5 accepts docker.exe only under %ProgramFiles%, %ProgramFiles(x86)% or %SystemRoot% (an allowlist, not an ACL check). B8 parses each shim with the interpreter named by its shebang (bash -n for today's #!/usr/bin/env bash shims; Claude Code's /bin/sh only execs the shebang) and parses each TypeScript entry with bun build --no-bundle into a temp directory; a failure refuses registration with the file and line. A5: braces gets ignoreUntil = 2026-12-31 only after confirming it is absent from the shipped lib/diagram-render/dist bundle; if it is present, A5 replaces or drops the importing package before shipping.
  • CEO-18 (rework owed by commits already on the branch). A1 (f0531a2): add the auth-probe call and the bash-only-construct lint (CEO-15) and the nested-source enumeration; bin/gstack-paths stays untouched. A2 (597e483): its _QUOTA_SIG (bin/gstack-codex-probe around line 287) and its tests (test/codex-model-probe.test.ts) map rate limits, "too many requests" and 429 to quota; split them per CEO-14, keep the reason name quota_exhausted everywhere (probe, lib/gate-outcomes.ts, docs anchor codex-quota-exhausted), and add the rate_limited row. A3 (a853e41): replace its heredoc-based bodies with CEO-12's file-write mechanism, extend its lint to the structural rule, the allowlist grammars and every quoting shape, and add the hostile-payload tests. A4 (ffde420): add the batch and narrowed-loop too_large paths and the seam-injected boundary tests (CEO-2). A5 (e77c532): run CEO-22's acceptance scan. The Codex watchdog fix (2e1d3e7, freeze the command before reaping its children) is in the wave without a plan item; it gets a regression test and a CHANGELOG line under A2's Codex reliability heading.
  • CEO-19 (Tier 2 tests and fallbacks). B7: a free test asserts the consent and publishing exception in the skill-start spawned block and both hook directives. B9: a free test on the generated ship text asserts the PR check comes before the Greptile dispatch; with no PR, gh missing or not logged in, or a non-GitHub remote, /ship skips the dispatch and records "Greptile: not run (); runs on the PR once it exists". B10: a free test asserts the new config-mismatch message text and the BROWSER.md WebGL line. B11: a free test with a stub gbrain covers code scope present, absent, gbrain below 0.60 and a probe error; below 0.60 or on a probe error, the current offer stays unchanged (no regression).
  • CEO-20 (B3 keeps its controls). B3's exemption applies only in source contexts: a file whose +++ b/ path is TypeScript or JSX, a value after a : type annotation, or a value inside {...}. A known secret prefix or a high-entropy check takes precedence over the exemption, so a YAML bare high-entropy token still flags.
  • CEO-21 (B8 refusal behavior, including upgrades). Setup parse-checks every hook shim and TypeScript entry in the staged tree before it swaps the new install into place. Any failure aborts the swap, so the previous working install (and every registration pointing into it) stays active; setup prints the file and line and exits non-zero. A fresh install with a failing hook registers none of the failing hooks, registers the rest, and exits non-zero. Tests cover a fresh install and an upgrade over an existing install with live registrations, where the new tree has one unparseable shim.
  • CEO-22 (A5 acceptance). #3040 closes only after osv-scanner, run with the workflow's --config=.osv-scanner.toml against both lockfiles on the wave head (or a dispatched weekly OSV workflow run), reports no unignored findings; the PR body links that run.
  • CEO-23 (what "fixed" means for a red case). A6 and A7 report measurements as "observed k/n after fix at ", never as a proven rate. A red case is called fixed only when there is a named causal change and every failure in the closing measurement is explained; 9 of 10 alone does not close it. Longer-term reliability comes from the weekly census, reported separately.
  • CEO-24 (Windows claims match the evidence). The PR states exactly what the Windows lane exercised. B12's Copilot interpreter choice and B5's actual Docker operation are reported as unverified until exercised on Windows. .github/workflows/windows-setup-e2e.yml gains one journey step after setup: start the browse server from the installed runtime root and pipe a two-command flow into browse chain, asserting real output (covers B2 and B4 end to end).
  • CEO-25 (B6 premise check). Before rewording the router, one live Claude Code check confirms that a skill set to "off" in skillOverrides is absent from the session's skill list. If it is not absent, B6 drops the "route only to skills in the session's list" sentence and ships only the wording change.
  • CEO-26 (contributors hear early). When each rewrite commit lands on the branch, the superseded contributor PRs (#3027, #3049, #3052, and #3039, #3045, #3048, #3032, #3029, #3047, #3016 as adopted) get a comment linking the commit and the credit line; they are closed at merge as planned.
  • CEO-27 (B11 decided on the first probe). B11 ships only if the live gbrain probe succeeds on the first try against gbrain 0.60 or later; otherwise it goes to TODOS.md (CEO-6) without further investigation in this wave.
  • CEO-28 (A1 keeps sourcing, with the reason recorded). A1 keeps sourced helpers because skills call their shell functions (_gstack_codex_timeout_wrapper, _gstack_codex_select_model, _gstack_egress_run) across a block and read the variables they set; ${(%):-%x} is chosen over functions_source for simplicity, not version support. Turning the probe into an executed subcommand (gstack-codex-probe select-model exec printing its status lines) goes to TODOS.md as the next step that removes the shell dependency.
  • DX-1 (A1 locates itself or says why it cannot). The two helpers share one self-locate block with one resolution order: ${BASH_SOURCE[0]} under bash or ${(%):-%x} under zsh, made absolute with $PWD when relative; else $GSTACK_ROOT/bin when GSTACK_ROOT is exported and holds the helper (the variable the runtime prelude in scripts/resolvers/runtime-root.ts already uses); else the helper fails loudly instead of guessing: gstack: cannot locate gstack-codex-probe (shell: <name>). Source it from bash or zsh, or export GSTACK_ROOT=<install dir>. https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location. The Claude-only default path in bin/gstack-codex-probe and the $(pwd) fallback in bin/gstack-egress-lib.sh (both in commit f0531a2) are removed. The class guard adds an unknown-shell case and a bare-name (source gstack-codex-probe via PATH) case. CEO-15's byte-for-byte lint applies after this rework; the two blocks committed in f0531a2 differ today and must be unified first. Outside CI, a missing zsh prints zsh not installed: install zsh to run the sourced-helper guard (CI requires it) and skips; under CI=true it fails, as A1 already states.
  • DX-2 (one token per state, absolute doc links). The rate-limit state is spelled rate_limited everywhere: probe output CODEX_MODE: unverified (rate_limited), probe state, gate reason and docs anchor codex-rate-limited. Its troubleshooting row says: at probe time the review still runs; a mid-review 429 means that review failed, so coverage is missing. Every new user-facing message prints a full https://github.com/garrytan/gstack/blob/main/docs/<file>.md#<anchor> URL (the pattern autoplan/bin/phase-publication-hook.ts already uses), so the link works from any project; CEO-7's anchor test checks these URLs.
  • DX-3 (quota cache has a way out). The quota_exhausted output prints Codex's own line, when the gstack cache expires, and the retry-now command: GSTACK_CODEX_PROBE_RETRY=1 bypasses the negative cache for one probe (documented in the anchor row, along with deleting <state root>/.codex-model-probe). The probe HINT committed in 597e483 ("usage or rate limit", no anchor) is rewritten to match. A stub test covers recovery: cached quota_exhausted, then GSTACK_CODEX_PROBE_RETRY=1 with a stub that now succeeds, gives MODEL_OK without changing model, config or credentials.
  • DX-4 (A4 recovery keeps the user's work). The too_large denial reads: "This session's journal is MiB, over the 32 MiB limit /autoplan can verify. Run /context-save, start a new session (resume and compact keep writing this journal), run /context-restore, then /autoplan ; or run /plan-ceo-review, /plan-devex-review and /plan-eng-review individually." followed by its full anchor URL. The docs row says the cap has no user override and why (the guard must read the whole parent journal to verify ownership).
  • DX-5 (no dead-end refusals). When a CEO-12 post cannot be sent (file write failed, or an identifier failed its grammar), the skill prints the cause with the actual value, the path of the body file it composed, and the exact safe command to post it by hand (for example gh pr comment <n> --body-file <path>). Branch names are validated with git check-ref-format --branch instead of a narrow regex, so valid refs with +, @ or non-ASCII characters are accepted. After a successful post the block deletes its temp file. B5's refusal names the docker.exe path it found, says a user-writable directory is untrusted for a child that carries registry credentials, gives the fix (install Docker Desktop under Program Files, or run /cso static-only), states it is not overridable, and carries its anchor. CEO-21's setup output lists the hooks it registered and skipped, says "this is a gstack bug; report :", and on an upgrade says "your previous install is still active". B4, when it leaves a real node_modules in place and the Node/Playwright check then fails, prints the command that fixes it and its anchor.
  • DX-6 (upgrades are protected before the checkout changes). bin/gstack-session-update fast-forwards the live checkout before setup runs (git merge --ff-only around line 198), and Claude Code runs hooks straight from that checkout, so CEO-21's staged check alone cannot protect auto-updates. The updater parse-checks every hook shim and TypeScript entry at the incoming revision (from git archive <incoming> into a temp directory) before the fast-forward, using the same check as CEO-21. A failure holds the update, the same way the existing BUN_HELD hold works, and records the reason and file:line in the update status. A test runs the updater against a fixture remote whose incoming revision has one unparseable shim, with existing registrations, and asserts the checkout did not move and the old hooks still run. A manual git pull followed by ./setup cannot be checked before the pull; setup reports it with DX-5's message.
  • DX-7 (measure-loop controls are named and capped). A6's controls are gstack config keys with the CEO-9 defaults: ship_measure_rule_trials (10), ship_measure_behavior_panels (4), ship_measure_judge_outputs (10), ship_measure_ask_per_trial_usd (2), ship_measure_budget_usd (25: a total cap per red case across baseline, repair rounds and panel scoring, enforced by the coordinator before each batch, including parallel workers), ship_measure_max_rounds (3) and ship_rerun_backend (local by default; ubicloud only when set, so Ubicloud spend is opt-in). /ship prints the per-kind table (trials, pass bar, panels, budget) before it runs; the same table is in docs/TESTING_INTERNALS.md and in the ship skill's user-facing text. When the budget is exhausted, /ship stops with the named-red report. A documented skip ("this red is infrastructure, do not measure") labels the case unmeasured in the PR body; it never counts as a pass. A6's "default 10" means the rule-kind default; behavior and judge cases use CEO-9's per-kind counts.
  • DX-8 (the target journey is measured). The PR records one timed run of the persona's journey on a macOS machine with zsh as the login shell: start of /gstack-upgrade (or ./setup) to the first skill preflight that shows CODEX_MODE: ready, and a second run with a stub quota response that shows CODEX_MODE: quota_exhausted with the reset line; it records steps, any restart needed and elapsed time against the 2-5 minute target. The implementer uses Garry's paired Mac when available; without a Mac the PR says the journey is unmeasured. A free CI check on the existing macOS runner sources the probe under zsh and runs _gstack_codex_select_model exec (no paid call).
  • DX-10 (DX amendments govern where they differ from CEO ones). Where a DX amendment and a CEO amendment differ, the DX amendment governs: DX-7's opt-in ship_rerun_backend replaces CEO-1's automatic Ubicloud choice when UBICLOUD_API_KEY is set; DX-5's git check-ref-format --branch replaces CEO-12's example branch regex; DX-2's rate_limited spelling replaces CEO-14's rate limited.
  • DX-9 (users can verify and learn what changed). Each issue-close comment (Tracker hygiene) includes a copy-paste check with its expected output; for #3024: zsh -c 'source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_select_model exec' should print CODEX_MODEL: <model> (exec; source: ...), not invalid. The CHANGELOG entry has a "What changes for you" list: the new messages and their links, quota_exhausted skipping Codex for the rest of the run, setup and the auto-updater refusing unparseable hooks, the router wording (with a one-line command users can run to update their own CLAUDE.md section), the B7 consent rule, and the DX-7 controls. B10's BROWSER.md line states the effect plainly: "set GSTACK_DISABLE_GPU=off to enable WebGL (off is the only recognized value)".
  • ENG-1 (rework inventory covers every landed commit). CEO-18's list is extended to every commit on the branch; each item ships only when its row says "matches" or its rework is done. B1 2328cd3: add CEO-17's negative controls. B2 cdc0fc7: see ENG-10. B3 4441c8f: see ENG-11. B6 e103e57: regenerate every host (bun run gen:skill-docs --host all; .agents/, .factory/, .kiro/, .opencode/, .slate/ still carry the old wording), update CLAUDE.md:532 and test/helpers/autoplan-preconfigured-fixture.ts:7 (CEO-5, CEO-16), run CEO-25's live check. B7 7bfe46b: matches. B8 659b197: see ENG-2 and ENG-3. B9 23520a9: PR: unknown must skip with the reason instead of dispatching, plus CEO-19's test. B10 5b06b7b: DX-9 wording check. B4 9618336 and B12 3df850f: see ENG-9 for B4; B12 matches (CEO-8's Windows curation entry still owed). A5 e77c532: the plan now records the two extra ignores it added (sprintf-js via roarr, katex in diagram-render) and their ignoreUntil = 2026-11-30 (consistent with the file's other entries, replacing CEO-17's 2026-12-31); each of the three new ignores names a tracking issue filed at ship time, as the file header requires.
  • ENG-2 (honest scope for the setup hook gate). On the Claude install, CANONICAL_GSTACK_ROOT (~/.claude/skills/gstack) is the git checkout the hooks run from, so by the time ./setup runs the new files are already live; there is no staged swap there. CEO-21's "abort the swap, previous install stays active" applies only to copy-based runtime roots. On the Claude checkout the setup gate refuses to register the failing hooks, registers the rest, reports file and line, and exits non-zero; it never prints DX-5's "your previous install is still active". DX-6 (the updater's pre-fast-forward check) is the protection for auto-updates. The landed gate's early exit 1 before any registration is replaced accordingly.
  • ENG-3 (the hook check covers what the hook runs). The hook list is derived from the registration code (every _hook_command_path argument plus the phase-publication hook), not a hand list; a free test asserts the two stay equal. Each TypeScript entry is checked with a local-import bundle into a temp directory (bun build --target=bun with node_modules packages external), so a broken or missing local import fails the check; a missing entry file fails instead of being skipped. Tests: broken imported module, missing entry, and an upgrade with existing registrations (both for setup and for DX-6's updater check, which runs after the existing BUN_HELD check).
  • ENG-4 (A2's run-scoped skip has a carrier). Skill blocks run in fresh shells, so "skip the remaining Codex calls in that run" is carried by the 15-minute negative cache; a test runs two consecutive fresh-shell preflight blocks against a quota stub and asserts the second makes no Codex call.
  • ENG-5 (self-locate works where it matters and fails loudly elsewhere). DX-1's fallback accepts a set (not necessarily exported) GSTACK_ROOT that holds the helper. The shared block ends with return 1 2>/dev/null || exit 1 after printing its message and before any bash/zsh-only syntax, so dash and sh reach the message instead of a syntax error. The generated preflight (scripts/resolvers/constants.ts, which today runs source ... 2>/dev/null || true) keeps the source step's diagnostic and reports a distinct CODEX_MODE: helper_unavailable with that line and its anchor, instead of falling through to not_authed. Class-guard cases: dash (message, no syntax error), relative-path source followed by cd, bare-name source via PATH, and the full generated preflight under each.
  • ENG-6 (quota and rate-limit matching reads Codex's error, not the echoed prompt). Both the probe's signatures and classifyOutsideReview's stderr matching read only Codex's own error lines (the trailing ERROR: / stream error lines), not the whole stderr, which echoes the session prompt. Tests: an echoed prompt that contains "usage limit" and insufficient_quota with a non-quota exit stays execution_failed; a 429 body that also carries insufficient_quota resolves to quota_exhausted (quota takes precedence over rate limit).
  • ENG-7 (Dockerfile.ci installs zsh). .github/docker/Dockerfile.ci adds zsh, as A1 already states, so the A1 guard runs (and fails loudly when missing) inside the eval container too.
  • ENG-8 (identifiers are shell-safe, not only git-valid). git check-ref-format --branch accepts fix;id and fix`id`, so DX-5's branch check also rejects shell metacharacters ($, backtick, ;, |, &, (, ), <, >, quotes, \, whitespace) and refuses with DX-5's message; numeric ids must match ^[0-9]+$. Identifiers that come from remote data (PR head branch names, file paths from a diff or PR) travel as data, read from the agent-written file into quoted variables, never substituted into shell source. Tests include metacharacter identifiers that git accepts.
  • ENG-9 (B4 uses the landed design, and B5 is honest about Windows Docker). B4 keeps the landed runtime design (9618336: the Windows runtime root starts the server from the source checkout's bundle through .source-path) instead of the plan's mklink /J junction, which avoids a runtime-root delete following the junction into the checkout. It adds a version-skew guard: when the runtime root's CLI version and the checkout's server version differ, the CLI refuses with an anchor and the ./setup fix. CEO-17's junction mechanics and CEO-8's junction test are replaced by a test that the runtime root resolves the checkout bundle and refuses on version skew; CEO-24's journey step becomes a real isolated install (the current windows-setup-e2e.yml runs bun run build but never ./setup), then the installed browse from an unrelated directory piping a two-command chain flow. B5 resolves the allowlisted roots through the known-folder API (PowerShell [Environment]::GetFolderPath), not the process environment, resolves the candidate's real path before the prefix check, and rejects reparse points; its refusal says "outside the trusted install locations". Because lib/cso/docker.ts accepts only unix:/// endpoints, B5 is scoped to discovery: with docker.exe bound, /cso reports a clear "native Windows Docker transport is not supported yet; static assessment only" outcome with an anchor, and native named-pipe transport goes to TODOS.md. #3028 is closed with that explanation, not as fixed operation.
  • ENG-10 (B2 never boots a daemon for empty input). Empty piped input, a TTY with no arguments, and a readFileSync(0) error (EAGAIN, EOF) all produce the usage error and exit 1 before ensureServer(). Tests run with no daemon and with browser dependencies unavailable.
  • ENG-11 (B3 classifies by context; replaces CEO-20's precedence rule). Identifiers like SessionState, exceed the 3.0 bits/char entropy threshold, so "entropy takes precedence" cannot hold. B3 instead passes the file path into the scanner (lib/redact-engine.ts options) and classifies a value as a source expression only in a TypeScript/JSX file after a : type annotation or inside a JSX {...} expression; entropy and known-prefix rules apply to everything classified as a literal. The landed word-shape heuristic (readsAsCodeName) is replaced. Controls that must flag: API_KEY=VelvetRiverOrbitSunset; in a .env/YAML file, password: {RiverStoneAmberCedar} in YAML, a JWT in a .ts file, a quoted literal, a brace-wrapped literal, a literal concatenated to a property read. Controls that must not flag: session: SessionState, and key={turn.requestId + turn.role} in .ts/.tsx.
  • ENG-12 (A6 runner owns the measurement; budget is an admission budget). A6 ships as a runner feature, scripts/ship-measure.ts, which /ship calls and which owns: per-kind aggregation (rule: N trials; behavior: panels of 3, each at its own 2-of-3 threshold, not one 2-of-12 panel as runCaseDiagnosis would produce today; judge: outputs scored by 3-sample panels, with standalone judge ids accepted), unique artifact directories per baseline, repair round and trial, the diagnostic label, and the per-case table. DX-7's ship_measure_budget_usd is an estimated admission budget: before admitting a batch it reserves the estimated cost of every concurrent trial (judges included), reconciles actual costs after each batch, and stops when reserved plus spent would exceed the budget; the PR table shows estimated and actual spend. Local reruns (CEO-1) cap parallelism at max(1, floor(cores / shard concurrency)), queue the rest and record the parallelism used. Free tests drive scripts/ship-measure.ts with fake runners for every kind, the budget stop and the unmeasured skip.
  • ENG-13 (A6 seeded case cannot pass without the fix). The stub fails a trial only while the list is unsorted and its slot number mod 10 is 2, 5 or 9; slots reset per measurement batch. The case asserts the closing measurement ran at least 10 trials against the sorted file, and a negative control (a "fix" that edits an unrelated line) re-measures at 7 of 10 and never reaches the gate command.
  • ENG-14 (A3 file location and fixed temp paths). The implementation verifies, against the Claude Code version pinned in .github/docker/Dockerfile.ci, where the host's write tool can create the body file without a permission prompt in default mode and in headless or spawned sessions (candidates: a project-local .gstack/tmp/ excluded through .git/info/exclude, or $GSTACK_STATE_ROOT/tmp), uses the first that works with mode 0600, and records the result in the PR. review/greptile-triage.md's fixed /tmp/greptile_line.json and /tmp/greptile_top.json move to mktemp paths.
  • ENG-15 (anchor test resolves locally). CEO-7's anchor test accepts both relative anchors and DX-2's https://github.com/garrytan/gstack/blob/main/docs/...#anchor URLs and resolves both against the docs in the working tree, so a new anchor does not fail CI before it reaches main.

Review record

Phase 0: Intake (autoplan)

  • Base branch: main (GitHub; origin/main 5885157a9, v1.91.29.0). Branch garrytan/fix-wave-oct6; the only change on it is this untracked plan file.
  • Design doc: gstack-design-doc-find returns this plan itself, so it is the problem/constraints source. No /office-hours doc; prerequisite offer not repeated (this plan is a triaged fix wave, not an exploration).
  • Scope: dxRequired=true (22 term matches: shell 6, command 5, Claude Code 3, CLI 2, argument 2, flag, SDK, SKILL.md, agent). UI scope: none (0 view/rendering terms) so Phase 2 (design) is skipped. gstack is also a developer tool and agents are its primary users, so DX would run regardless.
  • Restore point: /home/user/.gstack/projects/garrytan-gstack/garrytan-fix-wave-oct6-autoplan-restore-20261006-174857.md.
  • Context read: CLAUDE.md, TODOS.md (related entries: C8 router wording P3, Step 0E handoff/auto-decide-preserved history, placeholder convention for generated bash P3, gate-outcome telemetry P3), git log -30, design-doc lookup.

Phase 0.5: Outside reviewer preflight

CODEX_MODEL: gpt-6-astra (exec; source: /home/user/.codex/config.toml model), MODEL_OK, CODEX_MODE: ready. Codex runs for every phase's outside voice. (The preflight ran under bash; under zsh the same block would print CODEX_MODEL: invalid, which is A1.)

Phase 1: CEO review (SELECTIVE EXPANSION)

Pre-review system audit

  • No stashes. The branch diff against main is only this plan. Hot files in the last 30 days: release files, test/helpers/touchfiles-data.ts, ship goldens, plan-eng-review sections, docs/TESTING_INTERNALS.md, paid-retry tests. This wave touches the same ship/test-policy surfaces that #3033 is still editing.
  • Prior learnings: gstack-learnings-search returned none. Brain context: unreachable (no cache); treated as cold. Question tuning: off.
  • Retrospective: the October 3 severe wave (v1.91.19.0) already fixed three neighbors of this wave's items: C8 (router drops gstack-disabled skills), the Step 0E handoff helper (the last root cause of auto-decide-preserved reds), and #3026 defect 1. Each recurrence below is checked against that fix before new work is planned.
  • Taste calibration. Good references: lib/gate-outcomes.ts (one table, one anchor per reason, tested), _gstack_codex_model_probe (explicit contract comment, exit codes, cache signature). Patterns to avoid: keyword-heuristic fixes (the two-heuristic shape of PR #3048, the regex-per-spelling detector history in TODOS.md) and 4,600-line guard PRs for a 50-line check (#3032).
  • Landscape (web search, Layer 1): ${(%):-%x} is the accepted zsh equivalent of ${BASH_SOURCE[0]}; it is unreliable only inside nested functions called after sourcing (zsh 5.0.5 report), which the plan's resolve-once-at-source-time rule avoids. $0 and functions_source are the known wrong answers, matching the plan's rejections. Layer 3: nothing contradicts the conventional idiom.

0A. Premise challenges

  1. "Codex outside voice is broken for macOS zsh users since v1.91.16.0" (A1). Valid. Reproduced here with zsh 5.9: zsh -c 'cd /tmp && source bin/gstack-codex-probe && _gstack_codex_select_model exec' prints Module not found "/../scripts/resolve-codex-generation-model.ts" and CODEX_MODEL: invalid. bin/gstack-egress-lib.sh under zsh from /tmp resolves _gstack_egress_lib_dir=/tmp. Only three files use BASH_SOURCE (bin/gstack-codex-probe 5 sites, bin/gstack-egress-lib.sh, bin/gstack-paths); skills source gstack-codex-probe (33 rendered sites) and gstack-egress-lib.sh (15). gstack-paths is executed, not sourced, by skills, so its fix is defensive. Do-nothing cost: every outside pass on the default macOS shell reports model_unusable and the user loses cross-model coverage without knowing why.
  2. "A Codex quota error reads as inconclusive" (A2). Valid. The probe classifies only model 400/404 and broken-install signatures; anything else falls to MODEL_PROBE_INCONCLUSIVE and is never cached. Correction of detail: the re-probe costs one real Codex round trip per skill (up to the 30 s wrapper), not always 30 s.
  3. "Reply templates run backticks as shell" (A3). Valid. review/greptile-triage.md:102,108 and scripts/resolvers/preamble/generate-test-failure-triage.ts:74-75 confirmed. TODOS.md already carries "placeholder convention for generated bash" (P3); the A3 lint is the first enforced piece of it.
  4. "A >32 MiB journal is reported as a retryable identity failure" (A4). Valid, but the fix as written treats the symptom. lib/claude-public-transcript.ts:555 throws OwnedReadError('identity') on size; the hook retries inside a 2 s deadline. The plan's fallback ("start a new session") pushes users off /autoplan in exactly the long sessions where it gets used. The ancestry check reads the whole journal from its first user message, so lifting the cap needs a measured full read, not a tail read. Kept as written plus a measured, conditional cap raise (CEO-D6, taste).
  5. "The root router routes to skills the user disabled" (B6). Partly stale. Main already removes skills disabled in gstack's own disabled_skills from the rendered router (omitDisabledSkills, scripts/gen-skill-docs.ts:171-190, v1.91.19.0). What remains: skills turned off in Claude Code's skillOverrides (gstack cannot see them at render time) and the "When in doubt, invoke the skill" wording, which test/gen-skill-docs.test.ts:2117 and test/helpers/autoplan-preconfigured-fixture.ts:7 pin. Plan text corrected by baseline edit.
  6. "#3033 overlaps only cli.ts, cso/process.ts and redact-patterns.ts". False. pr3033-files.txt also lists .github/docker/Dockerfile.ci, .github/workflows/free-tests.yml, docs/troubleshooting.md, docs/TESTING_INTERNALS.md, review/SKILL.md.tmpl, TODOS.md and the release files, all of which this wave edits. Plan text corrected by baseline edit.
  7. "A6 belongs in a severe-fix wave". Accepted as the user's decision. #3034 is a feature, approved by Garry on 2026-10-05 for this wave, and it is the tool A7 uses. Kept.
  8. "A7's timeout is a new defect". Unproven. TODOS.md records five earlier auto-decide-preserved reds caused by the skipped Step 0E handoff line, fixed in v1.91.19.0 by gstack-ceo-mode-handoff. The first step must replay the captured run (37272185151) against that known class before paying for trials (CEO-D7).
  9. "B8: a hook that does not parse blocks every tool call". Valid, low frequency, high impact. The shims are #!/usr/bin/env bash files that Claude Code runs through /bin/sh (per the shim header); a syntax error exits 2, which a PreToolUse hook reads as "block". It needs a broken checkout to trigger, so the lean 50-line version is the right size.
  10. A5 reachability. Valid with a correction. fast-uri and hono arrive only through the dev dependency @anthropic-ai/claude-agent-sdk -> @modelcontextprotocol/sdk (eval harness, not shipped runtime); ip-address is runtime through socks (browse proxy). Overrides remain the right fix for all three; the browse proxy test run is the one that matters for ip-address.

0B. Existing code leverage

Sub-problem Existing code Plan reuses it?
A1 self-locate 3 BASH_SOURCE sites; bin/gstack-state-root.sh sourced through them Yes: one idiom replaces the sites; no new helper file
A2 quota outcome _gstack_codex_model_probe contract + cache file .codex-model-probe; GATE_OUTCOMES table; classifyOutsideReview execution() stderr path; test/troubleshooting-anchors.test.ts Yes: new probe exit code, new gate reason row, same cache signature
A3 safe bodies generated-bash lint (existing) Yes: extended, not new
A4 too_large HARD_CAUSE map in autoplan/bin/phase-publication-hook.ts:524; docs/autoplan-guard-troubleshooting.md Yes
A6/A7 measure loop scripts/test-paid-shards.ts --case, eval:pass-rates --run --case, failure_cause/failure_detail, detector replay corpora, test:ubicloud Yes, per #3034
B1 shell-built init shellInitAttempt-style diagnostic in the hook (PR #3045 design) Rewritten, binding unchanged
B6 router omitDisabledSkills Kept; prose covers skillOverrides
B8 hook parse setup hook registration via bin/gstack-settings-hook add-event, _hook_command_path Yes: check sits before registration
B5 Docker GSTACK_CSO_TRUSTED_GIT binding + TRUSTED_PATH in lib/cso/process.ts Yes: same pattern for Docker

No sub-problem rebuilds existing code.

0C. Dream state

  CURRENT STATE                         THIS PLAN                                12-MONTH IDEAL
  Outside voice silently missing on     One self-locate idiom + a class test     Every helper and generated block is
  zsh; quota/oversize/Windows failures  that sources every helper under bash     shell-portable by construction, every
  surface as the wrong cause; untrusted and zsh; quota/too_large/shell-init      failure prints a typed reason with a
  text reaches the shell; paid reds     reasons with fixes; a lint on unsafe     doc anchor, and a red eval is measured
  get rerun blind at ~$90/push.         bodies; /ship measures a red case.       and fixed before anyone reruns a gate.

The plan moves toward the ideal on all four axes. The remaining gap is coverage: the class guards cover sourced helpers and gh/glab/git/curl bodies, not every generated bash block.

0E. Mode

Mode: SELECTIVE EXPANSION; approved decisions: none. (printed by gstack-ceo-mode-handoff). Provenance: autoplan override (explicit), no question asked. The skill's file-count rule would recommend SCOPE REDUCTION (well over 15 changed files), but the user's standing preference is one PR per wave and autoplan fixes the mode, so scope stays whole and each expansion is decided individually. No new approach decision was needed in 0D.

0F/0G. HOLD checks and cherry-picks

HOLD checks:

  1. Complexity: about 45 files across 19 items, no new services. Fewer moving parts would mean splitting the wave, which contradicts the user's one-PR preference; each item is independently revertable as its own commit. Kept (CEO-D2).
  2. Minimum changes: each item is already the minimum for its promise. Deferral candidates considered and kept: B4 and B5 (Windows; verified by CI lanes only), B11 (already conditional). Kept (CEO-D3).
  3. Invariants: P1-P4 kept; repairs needed to meet them are in scope.

10x check: the 10x version is a typed failure taxonomy that every helper, probe and hook emits into (one reason table, one doc anchor each, telemetry per reason), so "names its real cause" is enforced by construction rather than item by item. This plan builds two more rows of that table (quota, too_large), which is the right increment; the telemetry half stays a TODO (gate-outcome telemetry, P3).

Platform potential: the shared self-locate idiom and its class test become the gate for every future sourced helper; the A6 loop becomes the standard answer to any red paid case.

Delight scan and cherry-picks (each auto-decided per the 6 principles; see Decision Audit Trail):

# Proposal Effort Risk Decision Reason
X1 A4: measure full owned-read+parse time for 64/128 MiB journals and raise the cap when it fits the hook's 2 s loop S Medium (guard code) ACCEPTED (taste) In blast radius, small; makes /autoplan work in long sessions instead of only explaining why it cannot
X2 A6: same measure-first rule for free-suite flakes (rerun the failing shard's exact file list N times, Ubicloud when configured), from #3034's design notes S Low ACCEPTED Same file as A6, <1 h CC, part of the approved issue the plan omitted
X3 A2: cache quota_exhausted until Codex's stated reset time S Medium (parsing free text) SKIPPED 15-min TTL plus the verbatim line covers it; parsing a localized timestamp is fragile (P5)
X4 Per-reason gate-outcome telemetry for quota/too_large S Low SKIPPED (duplicate) Already a TODO (gate-outcome telemetry, P3); not duplicated (P4)
X5 B9 full version: push early and run Greptile in parallel during /ship (#3020's real request) L Medium DEFERRED to TODOS.md New flow outside blast radius; the plan already defers it (P3)
X6 A6 paid case drives a fixture project whose eval command is a seeded local stub, so inner trials are free and fast S Low ACCEPTED (taste) Keeps the approved paid case inside the ~10-minute eval target without changing what it proves
X7 A7: replay the captured red run against the known Step 0E handoff class before paid trials S Low ACCEPTED AGENTS.md: reuse captured public events in free regressions before paying

Decision ledger (CEO)

ID and owner Contract and evidence Current Proposed Status Exact approval and scope
CEO-D1 (Step 0E) Mode SELECTIVE EXPANSION n/a approved autoplan override; mode only
CEO-D2 (0G HOLD 1) One PR, ~45 files keep whole wave split approved (keep) P6 + user's one-PR preference
CEO-D3 (0G HOLD 2) B4/B5/B11 deferral candidates keep defer approved (keep) P1; "reduce scope on a complete plan: no"
CEO-D4 (0A-5) B6 text partly stale plan text corrected text (baseline edit) approved factual correction only, behavior unchanged
CEO-D5 (0A-6) #3033 overlap list 3 files full list (baseline edit) approved factual correction only
CEO-D6 (X1) A4 cap 32 MiB + too_large measured conditional raise approved (taste, provisional) P2; surfaced at gate
CEO-D7 (X7) A7 first step measure 10x replay captured run first approved P1/AGENTS.md eval discipline
CEO-D8 (X2) A6 coverage paid only + free-suite flakes approved P2
CEO-D9 (X6) A6 paid case shape unspecified inner runner seeded stub eval command approved (taste, provisional) P1 + 10-minute eval preference; surfaced at gate
CEO-D10 (X3) quota cache TTL 15 min until reset time declined P5
CEO-D11 (X4) gate telemetry TODO P3 in wave declined (duplicate) P4
CEO-D12 (X5) Greptile in parallel TODO in wave deferred P3; TODOS.md entry written by the implementation PR
CEO-D13 (0A-4/0A-8) Test-pinning prose n/a tests updated with B6 wording approved P1; prompt cleanups audit pinning tests
  • CEO-1 (A6, free-suite flakes). As #3034 specifies, when a free-suite shard fails during /ship, /ship reruns that shard's exact file list (which also catches order-dependent failures) N times (default 10), in parallel, under a 10-minute wall-clock cap, with the runner's own flaky-retry turned off so retries never inflate the measured rate. This needs an executable runner change, not just workflow wording: scripts/ubicloud/test-free.sh sets GSTACK_FREE_RETRY_FLAKY=1 unconditionally today, and a free test proves the diagnostic launcher disables retries and records every outcome. Each rerun gets its own state (temp HOME, temp state root and its own port range), and the first rerun runs at the shard's original concurrency as the baseline; parallel reruns are labeled as such so contention they add is not read as the original failure. It runs the reruns on one Ubicloud VM, as N parallel processes, when UBICLOUD_API_KEY is set and the project documents a Ubicloud test command (gstack: bun run test:ubicloud); otherwise locally. The rerun uses the project's documented test command with the shard's file list as arguments; when the runner cannot take a file list, it reruns the whole failing command. It reports the pass rate and a failure capture before calling the failure pre-existing or pushing; when the cap hits, it reports the partial count (for example 6 of 10 completed). These reruns are labeled diagnostic and never change the recorded verdict. The free test that pins A6's steps also pins this step in the generated ship skill.
  • CEO-2 (A4 cap and follow-up). This wave keeps the owned-journal cap at 32 MiB (OWNED_TRANSCRIPT_MAX_BYTES, already in the working tree) and does not raise it on timing evidence: both outside reviews showed a larger cap only moves the same cliff. The summed multi-file batch read (lib/claude-public-transcript.ts around line 341) keeps its own 32 MiB constant and stops throwing a plain Error with a hard-coded "32 MiB": it reports too_large with the real size and cap, and so does the ownedParentTranscript-narrowed loop. Free boundary tests inject a small cap through a test seam and cover the owned read, the snapshot path and the narrowed loop: at the cap reads; one byte over yields too_large, never identity; a real identity mismatch still yields identity. No multi-MiB fixture enters the free suite. A P1 TODOS.md entry, targeted at the next wave, specifies a bounded or incremental owned-journal read that keeps every ownership and ancestry check; its acceptance is an /autoplan run that completes in a session whose journal is over 100 MiB, with phase-entry latency and peak memory measured on macOS and Linux developer machines.
  • CEO-3 (A6 paid case shape). The seeded-flake paid case covers only the measure, fix, re-measure and gate segment of /ship. Its fixture project has no remote; its CLAUDE.md documents a single-case command (./evals.sh case <id>) and a gate command (./evals.sh gate), both local stubs. The fixture's inner case is kind rule, so CEO-9's rule budget applies (10 trials, at least 9 passing). Each stub invocation claims the lowest free trial slot by creating slots/<n> with mkdir, which is atomic, so parallel trials never race; the trial in slot 2, 5 or 9 fails before the fix (3 of 10). Its failure output prints a failure_cause and failure_detail naming the nondeterministic line in one fixture file (it reads an unsorted list that the seed shuffles and compares it to a sorted expectation); the deterministic fix is to sort the list, after which it passes 10 of 10. The fixture's free test command passes instantly and its tree is clean, so /ship reaches the eval step at once; the stub's first gate run is red on the flaky case, and the case stops once /ship has run the gate command after the fix; it never reaches version, push or PR steps. Declared fixture answers: approve the CEO-9 spend question; any other question fails the case as a contract violation. Inner trials cost nothing and finish in seconds. The case is kind behavior (a panel of 3 independent trials, PASS at 2 or more with no contract violation), runs in the periodic tier, and asserts that /ship measured the case alone, fixed the named line, re-measured at or above 90%, and only then ran the gate command once.
  • CEO-4 (A7 replay first). A7 starts by replaying the captured transcript and public events of periodic run 37272185151 (auto-decide-preserved, outcome=timeout) against the known Step 0E handoff-line class (fixed in v1.91.19.0 by gstack-ceo-mode-handoff), as a free regression, before any paid trial. If the replay matches that class, it is a regression of that fix: fix it at the cause and keep the replay as a free regression test. If it does not match, the replay becomes the failure capture that starts A6's loop. The baseline measurement (10 trials alone, after #3033 lands) runs on main; the closing measurement runs on the wave head and needs at least 9 of 10 before #3038 is closed. If 3 repair rounds do not reach 9 of 10, A7 alone stops: the PR body carries the named-red report with every trial, #3038 stays open with that evidence, a P1 TODOS.md entry is filed, and the rest of the wave continues. This does not make any PR check red, because the case runs in the periodic lane.
  • CEO-5 (B6 pinned prose). The tests that pin "When in doubt, invoke the skill" (test/gen-skill-docs.test.ts around line 2117 and test/helpers/autoplan-preconfigured-fixture.ts line 7) are updated in the same commit as the wording, to assert the new when-to-invoke and when-not-to-invoke text. The TODOS.md "C8 router wording behind a routing panel" entry is closed with the routing-eval run link.
  • CEO-6 (TODOS.md writes). The implementation PR adds TODOS.md entries, in the repo's TODOS format, for: B9's full version (push early and run Greptile in parallel during /ship, #3020); B11 if the live gbrain probe cannot be confirmed; the P1 bounded or incremental owned-journal read (CEO-2); B5's side note if it does not reproduce (see CEO-10); the P1 entry for A7 if it stops red (CEO-4); every Tier 2 item reverted under CEO-11; the executed-subcommand probe (CEO-28); and a gstack helper that posts PR and issue text by passing arguments as an array, never through a shell string (the stronger form of CEO-12, deferred).
  • CEO-7 (P3 doc anchors). Every new failure state in this wave (a new refusal, denial or error, not a reworded informational line) has a doc anchor and a fix line: A2 quota_exhausted and rate_limited, B2, B4, B5 and B8's setup refusal in docs/troubleshooting.md; A4 too_large and B1's shell-built-init denial in docs/autoplan-guard-troubleshooting.md. B10's reworded config-mismatch message points to BROWSER.md's existing "Daemon discipline" section. Each message carries its anchor literally (docs/troubleshooting.md#<slug> or docs/autoplan-guard-troubleshooting.md#<slug>), and a free test enumerates every such reference in the source tree and checks the anchor exists (extending test/troubleshooting-anchors.test.ts).
  • CEO-8 (Windows coverage is named). Each Windows item names its free test and adds it to the curated Windows-safe subset (scripts/lib/windows-curation.ts): B2 the piped-stdin browse chain test against a stub daemon; B4 a setup test that the runtime root resolves playwright through the junction and that the Node load check runs against the runtime root; B5 a binding test for GSTACK_CSO_TRUSTED_DOCKER (accepted only from a non-user-writable directory, present in TRUSTED_PATH); B12 a generated-text test for the Copilot glossary line.
  • CEO-9 (A6 measurement defaults). The pass target is counted in trials of the case's kind: rule cases run 10 trials and need at least 9 passes; behavior cases run 4 panels of 3 (12 trials) and need every panel to pass and at least 11 of 12 trials; judge cases run 10 outputs, each scored by its 3-sample panel, and need at least 9 passing outputs. Before running, /ship always shows the estimate (per-trial cost from pass-rate history times trials). As #3034 specifies, it asks first when a case's per-trial cost is over a set amount (default $2 per trial) and, when the case has no pass-rate history, it shows "estimate unknown" and asks once. The repair-round limit is 3; after it, /ship stops with a named-red report. The eval command comes from the same discovery /ship already uses for test commands (CLAUDE.md/AGENTS.md, then package scripts); gstack's scripts/test-paid-shards.ts --tier <tier> --case <id> is the default; when no single-case command is documented, /ship asks once and records the answer in its report.
  • CEO-10 (B5 side note, bounded). The "Java repo detected as Python" side note becomes one fixture test: a Maven repository (pom.xml, Java sources) that also contains helper Python scripts must be detected as Java. Fix detection precedence only if that fixture misdetects; otherwise record the report in TODOS.md (CEO-6). Coordinating with the CSO thread (GSTA-24) is a process note, not a deliverable.
  • CEO-11 (Tier 2 "ship if green"). A Tier 2 item ships when its focused tests and every required CI check pass on the wave head. A Tier 2 item that stays red after diagnosis within the wave is reverted as its own commit and moved to TODOS.md with the failure evidence, and the PR continues. A red Tier 1 item holds the whole PR, with one exception: A7 may stop red under CEO-4, because its case runs in the periodic lane and no PR check depends on it. The PR is never called ready while any check is red.
  • CEO-12 (A3: free text never enters a shell command). The rule is structural: in a generated bash block, a free-text placeholder (bodies, titles, messages, error output, reviewer or diff text) may appear only as the contents of a file whose path came from mktemp; the command gets --body-file, -F body=@file or --title "$(cat "$file")". A bash block creates the private path with mktemp, the agent writes the text to it with the host's file-write tool, and the next block uses it. There is no heredoc fallback: if a host cannot write the file, the post is left unsent and the skill says so. Identifier placeholders that do enter commands (branch names, paths, PR numbers, comment ids) are on a reviewed allowlist, each with a validation grammar the skill applies before use (for example a branch name matching ^[A-Za-z0-9._/-]+$); a value that fails its grammar is not used. The lint fails on any non-allowlisted placeholder in a command argument of any command, in any quoting (double, single, or an unquoted heredoc), and reads \\ continuation lines as one command. Hostile-payload tests run through the real generated text: a body containing backticks, $(...), an apostrophe and a line equal to a common heredoc delimiter, and a branch name containing ;. The same commit fixes the literal \\n sequences and triple backticks inside the current test-failure templates (ship/sections/tests.md, generated from scripts/resolvers/preamble/generate-test-failure-triage.ts), which post broken issue bodies today even without untrusted input.
  • CEO-13 (shared-prompt edits keep eval coverage). B7 changes only the runtime skill-start spawned block and the hook directives (hosts/claude/hooks/spawned-directive.ts, hosts/claude/hooks/auq-error-fallback-hook.ts); it does not edit the static all-skill preamble (scripts/resolvers/preamble/generate-ask-user-format.ts), which keeps it consistent with the #3012 decision. A2's preflight change renders into every skill with an outside voice; its coverage is the free generated-preflight tests plus the outside-voice paid cases already in the PR gate (test/skill-e2e-outside-voice.test.ts, test/skill-e2e-outside-plan-disabled.test.ts). Validation adds AGENTS.md's cheap gates before any paid run: bun run gen:skill-docs freshness (all hosts), bun test test/parity-suite.test.ts, prompt-size and parity limits, the credential scan, bun run typecheck, bun run typecheck:test, test/module-size-ratchet.test.ts and test/spawnsync-timeout-tripwire.test.ts.
  • CEO-14 (A2 quota versus rate limit). Only Codex's usage-limit line ("You've hit your usage limit", including its reset time) and insufficient_quota map to quota_exhausted (exit 4, cached 15 minutes). A plain HTTP 429 or rate-limit line maps to a separate rate_limited outcome (exit 0, like the inconclusive contract, with _GSTACK_CODEX_PROBE_STATE=rate_limited): never cached, printed verbatim, reported as CODEX_MODE: unverified (rate limited), and the review's own result still decides. Stub tests: usage-limit line with exit 1 gives quota_exhausted and is cached; insufficient_quota gives quota_exhausted; a bare 429 gives rate_limited and is not cached; any of these lines with exit 0 stays MODEL_OK; a timeout keeps the inconclusive contract.
  • CEO-15 (A1 nested sources). A1's class guard enumerates sourced files transitively: every file the rendered skills source, plus every file those helpers source in turn (for example bin/gstack-state-root.sh, sourced by bin/gstack-codex-probe). Under both shells it makes one real call each to the model resolver, the auth probe, the state root and the egress home. The lint allows BASH_SOURCE only inside the one shared self-locate block, matched byte for byte across the helpers, and fails on other bash-only constructs in sourced helpers (${!name} indirection, declare -n, mapfile/readarray, [[ ... =~ ... ]] with BASH_REMATCH).
  • CEO-16 (B6 both texts). B6 changes both places that say "When in doubt, invoke the skill": the root router (SKILL.md.tmpl) and the CLAUDE.md routing-injection text that bin/gstack-skill-start (around line 498) offers to new projects, plus gstack's own CLAUDE.md routing section. gstack never rewrites a user's existing committed CLAUDE.md; the release note shows the new wording for users who want to update theirs.
  • CEO-17 (Tier 2 mechanics named). B1 detects a shell-built init when a Bash call names the snapshot tool or its init argument through any of: a variable holding the tool path, $(...) or backticks around the tool or its arguments, &&/;/|| chains, a pipe, or a redirect; negative controls keep the generic denial for bun "<SNAPSHOT_TOOL>" scope ... and for an unrelated bun other.ts init. B2: with no arguments, chain reads stdin only when stdin is not a TTY; a TTY with no arguments, or empty piped input, gets the existing usage error and exit 1 instead of waiting. B4: setup creates the junction from Git Bash with cmd //c mklink /J, replaces a stale junction, leaves an existing real node_modules directory untouched and says so. B5 accepts docker.exe only under %ProgramFiles%, %ProgramFiles(x86)% or %SystemRoot% (an allowlist, not an ACL check). B8 parses each shim with the interpreter named by its shebang (bash -n for today's #!/usr/bin/env bash shims; Claude Code's /bin/sh only execs the shebang) and parses each TypeScript entry with bun build --no-bundle into a temp directory; a failure refuses registration with the file and line. A5: braces gets ignoreUntil = 2026-12-31 only after confirming it is absent from the shipped lib/diagram-render/dist bundle; if it is present, A5 replaces or drops the importing package before shipping.
  • CEO-18 (rework owed by commits already on the branch). A1 (f0531a2): add the auth-probe call and the bash-only-construct lint (CEO-15) and the nested-source enumeration; bin/gstack-paths stays untouched. A2 (597e483): its _QUOTA_SIG (bin/gstack-codex-probe around line 287) and its tests (test/codex-model-probe.test.ts) map rate limits, "too many requests" and 429 to quota; split them per CEO-14, keep the reason name quota_exhausted everywhere (probe, lib/gate-outcomes.ts, docs anchor codex-quota-exhausted), and add the rate_limited row. A3 (a853e41): replace its heredoc-based bodies with CEO-12's file-write mechanism, extend its lint to the structural rule, the allowlist grammars and every quoting shape, and add the hostile-payload tests. A4 (ffde420): add the batch and narrowed-loop too_large paths and the seam-injected boundary tests (CEO-2). A5 (e77c532): run CEO-22's acceptance scan. The Codex watchdog fix (2e1d3e7, freeze the command before reaping its children) is in the wave without a plan item; it gets a regression test and a CHANGELOG line under A2's Codex reliability heading.
  • CEO-19 (Tier 2 tests and fallbacks). B7: a free test asserts the consent and publishing exception in the skill-start spawned block and both hook directives. B9: a free test on the generated ship text asserts the PR check comes before the Greptile dispatch; with no PR, gh missing or not logged in, or a non-GitHub remote, /ship skips the dispatch and records "Greptile: not run (); runs on the PR once it exists". B10: a free test asserts the new config-mismatch message text and the BROWSER.md WebGL line. B11: a free test with a stub gbrain covers code scope present, absent, gbrain below 0.60 and a probe error; below 0.60 or on a probe error, the current offer stays unchanged (no regression).
  • CEO-20 (B3 keeps its controls). B3's exemption applies only in source contexts: a file whose +++ b/ path is TypeScript or JSX, a value after a : type annotation, or a value inside {...}. A known secret prefix or a high-entropy check takes precedence over the exemption, so a YAML bare high-entropy token still flags.
  • CEO-21 (B8 refusal behavior, including upgrades). Setup parse-checks every hook shim and TypeScript entry in the staged tree before it swaps the new install into place. Any failure aborts the swap, so the previous working install (and every registration pointing into it) stays active; setup prints the file and line and exits non-zero. A fresh install with a failing hook registers none of the failing hooks, registers the rest, and exits non-zero. Tests cover a fresh install and an upgrade over an existing install with live registrations, where the new tree has one unparseable shim.
  • CEO-22 (A5 acceptance). #3040 closes only after osv-scanner, run with the workflow's --config=.osv-scanner.toml against both lockfiles on the wave head (or a dispatched weekly OSV workflow run), reports no unignored findings; the PR body links that run.
  • CEO-23 (what "fixed" means for a red case). A6 and A7 report measurements as "observed k/n after fix at ", never as a proven rate. A red case is called fixed only when there is a named causal change and every failure in the closing measurement is explained; 9 of 10 alone does not close it. Longer-term reliability comes from the weekly census, reported separately.
  • CEO-24 (Windows claims match the evidence). The PR states exactly what the Windows lane exercised. B12's Copilot interpreter choice and B5's actual Docker operation are reported as unverified until exercised on Windows. .github/workflows/windows-setup-e2e.yml gains one journey step after setup: start the browse server from the installed runtime root and pipe a two-command flow into browse chain, asserting real output (covers B2 and B4 end to end).
  • CEO-25 (B6 premise check). Before rewording the router, one live Claude Code check confirms that a skill set to "off" in skillOverrides is absent from the session's skill list. If it is not absent, B6 drops the "route only to skills in the session's list" sentence and ships only the wording change.
  • CEO-26 (contributors hear early). When each rewrite commit lands on the branch, the superseded contributor PRs (#3027, #3049, #3052, and #3039, #3045, #3048, #3032, #3029, #3047, #3016 as adopted) get a comment linking the commit and the credit line; they are closed at merge as planned.
  • CEO-27 (B11 decided on the first probe). B11 ships only if the live gbrain probe succeeds on the first try against gbrain 0.60 or later; otherwise it goes to TODOS.md (CEO-6) without further investigation in this wave.
  • CEO-28 (A1 keeps sourcing, with the reason recorded). A1 keeps sourced helpers because skills call their shell functions (_gstack_codex_timeout_wrapper, _gstack_codex_select_model, _gstack_egress_run) across a block and read the variables they set; ${(%):-%x} is chosen over functions_source for simplicity, not version support. Turning the probe into an executed subcommand (gstack-codex-probe select-model exec printing its status lines) goes to TODOS.md as the next step that removes the shell dependency.

0H. CEO plan and spec review loop

  • CEO scope summary: /home/user/.gstack/projects/garrytan-gstack/ceo-plans/2026-10-06-fix-wave-oct6.md (written, read back). Amendment checkpoint: /home/user/.gstack/projects/garrytan-gstack/autoplan-ceo-fgTQov/ceo-implementation.md. Final spec input: /home/user/.gstack/projects/garrytan-gstack/autoplan-ceo-7cqX6C/ceo-implementation.md (111 lines, read in full).
  • Iteration 1 (score 6, FAIL, 19 issues): P3 anchors missing for B1/B2/B4/B5; Windows tests unnamed; AGENTS.md cheap gates missing; batch-path cap and hard-coded "32 MiB"; A4 text contradicted CEO-2; amendments had no heading; A6 units/defaults undefined; eval-command lookup; CEO-3 kind; CEO-4 branches; Tier 2 "green" undefined; A3 lint narrower than P2; B5 side note open-ended; CEO-1 feasibility. Fixed by CEO-2 rewrite, CEO-6 to CEO-13, the A4 baseline edit and the amendments heading.
  • Iteration 2 (score 7, FAIL, 21 issues): owned-read path through ownedParentTranscript; transient 429 cached as quota; A7 repair-limit exit; no-history spend estimate; heredoc delimiter collision; nested sources; B8/B10 anchors; B6's two texts; drift from #3034 wording (file list, per-trial cap); main-vs-head measurement; CEO-3 fixture detail; Ubicloud signal; timing statistic; B8 interpreter; B1 shapes; B4/B5 mechanics; braces date; B2 TTY; boundary-test cost; CEO-3 stopping point. Fixed by revised CEO-1/2/3/4/7/9/12 and CEO-14 to CEO-17.
  • Iteration 3 (score 6, FAIL, 26 issues; cap reached): stale base (#3033 merged at 55f8151; A1 f0531a2, A3 a853e41, A2 597e483 landed on the branch during this review), other quoting shapes, rate_limited exit code and mid-review classification, auth-probe and bash-only constructs, OSV acceptance, Tier 2 tests/fallbacks, TODO cross-refs, heredoc check ownership, summary drift, Tier 1 exception, A2 commit contradicting CEO-14 and its reason name, amendments not folded into items, 64 MiB step, CEO-3 parallel slot/kind/start, CEO-12 host/temp path, anchor source of truth, idiom text, B8 refusal mode, CEO-12/CEO-16 summary scope, gstack-paths is executed not sourced, B3 control conflict, Ubicloud retry inflation and VM layout, per-call cost. After the loop, CEO-18 to CEO-22, revisions to CEO-1/2/3/6/7/11/12/14/15 and five more baseline edits (base, A1 gstack-paths, A2 match lines, A2 reason rows, A3 class guard) address 25 of 26; not reviewer-confirmed. Open: amendments still override item text via the heading rule rather than being folded into A6 and the Tier 2 items.
  • Metrics appended to analytics/spec-review.jsonl: iterations 3, issues_found 66, issues_fixed 38 (iteration 1 and 2 issues not re-raised by the next review), remaining 26, quality_score 6.
  • Document approval (0H A/B/C): auto-decided A) Approve these documents and continue to 0I (P6; both documents reflect the decisions). Approves document versions only.

0I. Temporal interrogation

  HOUR 1 (foundations):   #3033 merged (55f8151) and A1/A2/A3 are already committed; start from CEO-18's rework list,
                          not from scratch. Read lib/gate-outcomes.ts + test/troubleshooting-anchors.test.ts before
                          adding any message (CEO-7). Install zsh locally (present on the Capy image: zsh 5.9).
  HOUR 2-3 (core logic):  Ambiguities: which placeholders go on CEO-12's allowlist (expect dozens of path/branch
                          placeholders); where /ship's eval step sits in ship/sections/tests.md.tmpl (Step 6.3, line ~108)
                          and how CEO-1's shard file list is recovered from the free runner's output.
  HOUR 4-5 (integration): Surprises: the A2 preflight edit regenerates every skill with an outside voice (golden and
                          parity-suite churn); B6 wording touches tests pinned in two places; B7 must stay out of the
                          static preamble; #3033's edits to ship/review sections may move anchors the A3 lint reads.
  HOUR 6+ (polish/tests): They will wish they had: the A7 replay fixture captured before any paid trial; the CEO-2
                          timing numbers recorded; Windows tests registered in scripts/lib/windows-curation.ts early so
                          the Windows lane runs them on the first push.

Effort: human team about 2 weeks; CC + gstack about 6-8 hours of implementation plus paid runs (A6 seeded case panel, A7 10-trial measurement, B6 routing panel, PR gate). Feasibility blockers: none. Pending choices: none beyond the taste decisions surfaced at the gate.

ID and owner Contract and evidence Current Proposed Status Exact approval and scope
CEO-D14 (0H spec loop) Spec findings, iterations 1-3 plan as amended CEO-7 to CEO-22 + baseline edits approved P1/P5 per finding; auto-decided
CEO-D15 (CEO-4/CEO-11) A7 may stop red after 3 rounds; wave continues A7 Tier 1 holds PR A7-only exception approved (taste, provisional) P6; surfaced at gate (alternative: PR waits for A7)
CEO-D16 (CEO-14) 429 vs quota committed code maps 429 to quota split, rate_limited uncached approved P1; requires rework of 597e483
CEO-D17 (CEO-16) B6 texts router only router + injection + gstack CLAUDE.md approved (taste, provisional) P1/P5; never rewrite user files
CEO-D18 (0H approval) Documents n/a approve approved auto-decided A

Dual voices (CEO)

  • Voice input: /home/user/.gstack/projects/garrytan-gstack/autoplan-ceo-243P7X/ceo-implementation.md (sha256 097e02e4...4acb), created after the spec loop.
  • Native CEO voice (Capy subagent, nativeDispatchPrompt verbatim): completed; returned INPUT: ceo 097e02e48d86a22b2f0dc2b58ec51ab4da2d35492a4920a1cb294454dcdc4acb (matches). Findings: (1) CRITICAL the zsh fix is held by a 19-item PR because A6 is Tier 1; (2) HIGH A1 fights sourcing instead of removing it; (3) HIGH raising A4's cap only postpones the cliff; (4) HIGH CEO-12's lint is a heuristic shell parser and the heredoc fallback relies on model compliance; (5) MEDIUM 9/10 is not a measured rate; (6) MEDIUM plan drifted from the branch (watchdog 2e1d3e7, OWNED_TRANSCRIPT_MAX_BYTES already in tree); (7) MEDIUM contributor goodwill; (8) MEDIUM Tier 2 is broad (B6, B11, B10). Premises it flagged as assumed: Capy machine as CI class, every host has a file-write tool, <placeholder> covers all untrusted text, skillOverrides reflected in the session skill list. Bottom line: approve with 1-4 applied; Garry decides on a hotfix split.
  • Codex CEO voice (gpt-6-astra, codex exec, read-only sandbox): OUTSIDE_STATUS: completed provider=codex host=claude, exit 0. Full output:
1. **High — The release bundles emergency repairs with a new product policy.** A3 addresses command execution; A5 addresses reported vulnerable dependencies. Yet CEO-11 lets A6's new eval workflow hold both indefinitely. Separate commits do not provide independent release or rollback boundaries. The plan never establishes why users exposed to those defects should wait for generic flake management.
   **Fix:** Ship the security and blocking compatibility repairs independently. Put A6 and the associated eval-policy changes in a separate PR with their own acceptance criteria.
2. **Critical — P2's security promise still depends on the agent handling hostile text correctly.** CEO-12 exempts paths and branch names by category, although repository-controlled names can contain shell syntax. Its fallback also asks the model to notice a heredoc delimiter collision before executing the payload. That leaves the security boundary dependent on the same agent composing the command. Template lint cannot establish that runtime values remain data.
   **Fix:** Require a structured data path into a helper that invokes commands with argument arrays. Validate any exempt identifier against an explicit grammar. Where safe text transport is unavailable, leave the operation unsent. Test hostile payloads through the actual execution path, including filenames and delimiter collisions.
3. **High — "Nine out of ten" is being promoted from a diagnostic observation into a repair verdict.** A7 closes the issue at 9/10, although `auto-decide-preserved` is classified as a rule case (test/helpers/touchfiles-data.ts:1708). Even assuming independent trials, an 80%-reliable implementation has approximately a 38% chance of passing that threshold. Labeling the runs "diagnostic" protects the historical record; it does not justify declaring the defect fixed.
   **Fix:** Use the measured rate to guide investigation. Require a demonstrated causal repair and explain every remaining failure before closing a rule defect. Report longer-term reliability separately from repair acceptance.
4. **Medium — The free-flake measurement changes the conditions it claims to measure.** CEO-1 prescribes ten concurrent reruns on one VM. Without resource and state isolation, that can measure contention introduced by the experiment instead of the original failure. The existing Ubicloud wrapper (scripts/ubicloud/test-free.sh:13) also explicitly enables retries, so this requires an executable runner change, not just workflow wording.
   **Fix:** Preserve the original concurrency and environment for baseline measurements. Give parallel trials isolated state and sufficient resources; label deliberate contention tests separately. Prove the diagnostic launcher disables retries and preserves every outcome before making this mandatory for users.
5. **High — B8 protects new registrations while leaving affected customers blocked.** CEO-21 explicitly preserves existing registrations when their implementation fails parsing. Existing hooks point into the installed tree, and the current healer retains hooks based on executable availability (bin/gstack-settings-hook:815). An executable file with invalid syntax can therefore remain registered after setup detects the problem. A nonzero setup exit does not restore the user's session.
   **Fix:** Make upgrade recovery part of acceptance. Validate staged hook implementations before activation and retain the previous working implementation on failure. For an already broken installation, provide a deterministic recovery path that preserves required enforcement. Test an upgrade with an existing registration, not only fresh installation.
6. **High — "Windows verified" exceeds the proposed evidence.** B2 uses a stub daemon; B4 proves module resolution; B5 proves executable binding; B12 proves a sentence exists. Those checks do not establish that an installed user can launch Chromium, execute a piped flow, or have Copilot choose the correct interpreter. Supporting more hosts without proving their first successful workflow creates recurring onboarding failures and gives users a reason to choose simpler host-native tooling.
   **Fix:** Add a Windows acceptance journey from installation through a real browser operation and piped chain, including an upgrade. Report Copilot execution and Docker operation as unverified until exercised; generated wording is not behavioral coverage.
7. **Medium — A4 postpones failure for the users most invested in the product.** CEO-6 acknowledges the need for bounded reading but leaves it as an undated TODO. Choosing a larger cap from Linux median timings preserves the same eventual session cliff. The reader still loads the entire journal and parses its history (lib/claude-public-transcript.ts:560); restarting abandons accumulated context precisely when orchestration should be most valuable.
   **Fix:** Ship the accurate diagnosis as the immediate repair, but assign a dated follow-up for bounded or incremental reading that preserves ownership checks. Measure complete phase-entry latency and peak memory on supported developer environments, and make long-session completion the product acceptance criterion.

Recommendation: Split the wave and revise acceptance criteria because urgent repairs currently depend on an unproven eval policy while shell safety, upgrade recovery, and Windows usability remain insufficiently demonstrated.

(The validator also printed VERDICT: clean / FINDINGS: none because the CEO voice output carries no [P0]-style severity tags; the findings above are real and integrated below. Markdown links in Codex's output were flattened to path:line text here.)

CEO DUAL VOICES — CONSENSUS TABLE:
  Dimension                            Claude              Codex               Consensus
  1. Premises valid?                   yes, 4 assumed      yes; P2 premise     CONFIRMED (core valid; P2 and B6 premises need proof)
  2. Right problem to solve?           yes                 yes (execution)     CONFIRMED
  3. Scope calibration correct?        no: A6 holds A1-A5  no: split A6 out    CONFIRMED concern -> USER CHALLENGE UC1
  4. Alternatives sufficiently explored? no: exec helpers,  no: arg-array       CONFIRMED gap (taste T5, T8)
                                       structural lint     helper, staged hooks
  5. Competitive/market risks covered? no: contributor     no: Windows first   CONFIRMED gap (different risks; CEO-24, CEO-26)
                                       goodwill            journey
  6. 6-month trajectory sound?         no: A4 cliff, 9/10  no: A4 cliff, 9/10  CONFIRMED concern (CEO-2 revised, CEO-23)
CONFIRMED = completed subagent + outside; primary cannot replace outside.

Integration (each finding's disposition):

  • Split A6 out / hotfix A1-A5 first (Claude 1 + Codex 1): both models recommend changing the user's one-PR, A6-in-Tier-1 structure. User Challenge UC1, not auto-decided; the original direction stands. Factual note for the gate: CEO-3 already runs A6's paid case in the periodic tier, so the paid case cannot turn a PR check red; what can hold the PR is A6's free tests and its /ship prose.
  • A4 cap (Claude 3 + Codex 7): both reject raising the cap on timing. CEO-2 revised: cap stays 32 MiB, batch and narrowed-loop paths fixed, P1 dated follow-up with a long-session acceptance. Reverses CEO-D6 (X1 now DEFERRED). Mechanical (both voices + P5).
  • 9/10 (Claude 5 + Codex 3): CEO-23 added. Mechanical.
  • P2 structural rule and no heredoc fallback (Claude 4 + Codex 2): CEO-12 rewritten (structural rule, identifier grammars, unsent on failure, hostile-payload tests). The argument-array helper (Codex 2) is the stronger form: taste T8, deferred to TODOS (CEO-6).
  • Free-flake isolation and runner retry (Codex 4): CEO-1 revised. Mechanical (P1).
  • B8 upgrade recovery (Codex 5): CEO-21 rewritten to validate the staged tree before swap. Accepted (P1; in blast radius of B8).
  • Windows evidence (Codex 6): CEO-24 (honest PR claims + one windows-setup-e2e journey step). Taste T6 for the journey step.
  • A1 executed subcommands (Claude 2): taste T5; recommend keep sourcing (already committed, skills call shell functions across a block) and record the reason plus a TODO (CEO-28).
  • Plan drift (Claude 6): Context and CEO-18 updated with every commit on the branch at review time; the watchdog commit gets a test and CHANGELOG line.
  • Contributor goodwill (Claude 7): CEO-26. Mechanical (P6).
  • Tier 2 breadth (Claude 8): B11 pre-declared (CEO-27, mechanical). Deferring B6 is taste T7; recommend keep (real user bug #3018, routing panel already required).
  • B6 premise (Claude premises): CEO-25. Mechanical.

Section 1: Architecture Review

Current scope: mode SELECTIVE EXPANSION (autoplan override). Accepted: Tier 1 A1-A7, Tier 2 B1-B12, amendments CEO-1 to CEO-28 (CEO-D1..D18 plus the voice dispositions above). Deferred: B9 full Greptile-in-parallel, bounded owned-journal read (P1), executed-subcommand probe, argument-array posting helper. Rejected: X3 (quota cache until reset), X4 (duplicate telemetry TODO). Pending at the gate: UC1 and taste T1-T8.

                         generated SKILL.md blocks (bash or zsh, Claude Code / Codex / Copilot hosts)
                                   |                       |                         |
                 source/.          |   gh/glab/git/curl    |    PreToolUse hooks     |  ./setup
                                   v                       v                         v
  bin/gstack-codex-probe --(nested .)--> gstack-state-root.sh     mktemp file  <-- agent file-write tool   (A3/CEO-12)
     |  self-locate idiom (A1)                                          |
     |  _gstack_codex_model_probe --> quota_exhausted | rate_limited (A2/CEO-14) --> lib/gate-outcomes.ts rows --> docs anchors (CEO-7)
     v                                                                   
  codex exec --> outside-review-result.ts classifyOutsideReview (stderr: quota/rate) 
  bin/gstack-egress-lib.sh (A1)                                   autoplan/bin/phase-publication-hook.ts
                                                                    |-- shellInitAttempt diagnostic (B1)
  ship/sections/tests.md.tmpl                                       '-- readOwnedClaudePublicTranscript -> too_large (A4/CEO-2)
     |-- measure loop (A6, CEO-1/3/9/23) --> scripts/test-paid-shards.ts --case / test:ubicloud (retry off)
     '-- Greptile PR check (B9)
  setup --> staged tree parse check (B8/CEO-21) --> swap --> gstack-settings-hook add-event
        --> Windows junction + Node/Playwright check at runtime root (B4) --> GSTACK_CSO_TRUSTED_DOCKER (B5)
  browse/src/cli.ts chain stdin (B2), config-mismatch text (B10); lib/redact-patterns.ts (B3); router + skill-start (B6/B7)

New coupling: lib/gate-outcomes.ts gains two Codex-specific rows (justified: it is the one reason table). The A6 loop couples /ship to scripts/test-paid-shards.ts --case only through the documented-command discovery (CEO-9), so other projects are not coupled to gstack's runner. Scaling: not applicable to a CLI/skill toolkit beyond the hook's per-phase-entry journal read (bounded at 32 MiB). Single points of failure: the codex probe (every outside voice routes through it; A1 and A2 are its two failure classes) and setup's hook registration (B8). Rollback: each item is its own commit; revert the commit and re-run ./setup (minutes). Security architecture: no new endpoints; the new trust decisions are Docker binding (B5, allowlisted directories) and free-text transport (A3). Production failure per integration point: Codex 429 (handled, CEO-14), journal growth (handled with explicit too_large, follow-up P1), Windows junction stale (CEO-17), staged hook parse failure (CEO-21). Elegant-to-a-new-engineer: one self-locate block, one reason table, one structural rule for free text. Platform: the measure loop and the reason table are reusable by every later skill. Issues found: 3 (hotfix coupling -> UC1; codex probe as single point of failure, accepted as is with A1/A2 guards; staged hook swap, CEO-21).

Section 2: Error & Rescue Map

  METHOD/CODEPATH                         | WHAT CAN GO WRONG                     | CLASS / REASON
  ----------------------------------------|---------------------------------------|---------------------------
  codex-probe self-locate (A1)            | sourced from zsh, nested function      | wrong dir -> CODEX_MODEL invalid
  _gstack_codex_model_probe (A2)          | usage limit / insufficient_quota       | quota_exhausted (exit 4)
                                          | HTTP 429 / rate limit                  | rate_limited (exit 0, unverified)
                                          | timeout 124                            | inconclusive (unchanged)
                                          | stale cache after reset                | quota cache 15 min TTL
  classifyOutsideReview stderr (A2)       | quota or 429 mid-review                | unavailable (quota_exhausted|rate_limited)
  generated gh/glab posts (A3)            | file-write tool missing                | post unsent, stated
                                          | identifier fails grammar               | value not used, stated
  readOwnedClaudePublicTranscript (A4)    | journal > 32 MiB (owned/snapshot/loop) | too_large (hard cause)
  osv-scanner (A5)                        | new advisory before merge              | scan red -> fix or justified ignore
  /ship measure loop (A6)                 | no single-case command                 | ask once, record
                                          | no cost history / over $2 per trial    | ask once
                                          | 3 rounds without 9/10                  | named-red report, stop
                                          | Ubicloud unavailable                   | local reruns
  phase-publication-hook (B1)             | shell-built init                       | specific denial + literal rerun
  browse chain (B2)                       | TTY or empty stdin                     | usage error, exit 1
  setup junction (B4)                     | stale junction / real node_modules     | replace / leave + report
  CSO docker binding (B5)                 | docker.exe outside allowlist           | not bound; anchor names fix
  setup hook parse (B8)                   | unparseable shim on upgrade            | swap aborted, old install active, exit != 0
  Greptile dispatch (B9)                  | no PR / gh missing / GitLab            | skipped, recorded with reason
  sync-gbrain offer (B11)                 | gbrain < 0.60 / probe error            | current offer unchanged

  EXCEPTION CLASS / REASON   | RESCUED? | RESCUE ACTION                                   | USER SEES
  ---------------------------|----------|-------------------------------------------------|-------------------------------
  quota_exhausted            | Y        | skip remaining Codex calls; cache 15 min        | Codex's line + reset time + anchor
  rate_limited               | Y        | proceed unverified; review decides              | verbatim line, "rate limited"
  too_large                  | Y        | hard deny with fallback                         | size, cap, start new session / run reviews by hand
  post unsent                | Y        | stop that post                                  | "body not sent: <reason>"
  named-red (A6/A7)          | Y        | stop loop, PR body report                       | per-case table with every trial
  swap aborted (B8)          | Y        | keep old install                                | file:line, exit non-zero
  wrong dir under zsh (A1)   | Y (fix)  | idiom resolves at source time                   | correct CODEX_MODEL line

No catch-all handlers are introduced; every rescued state has a named reason and a visible message. GAPS: 0 after CEO-12/14/21 (before the review: 3 — 429 cached as quota, heredoc delimiter collision, broken registration kept on upgrade).

Section 3: Security & Threat Model

Threat Likelihood Impact Mitigated?
Untrusted reviewer/diff/error text runs as shell in reply or issue posts (A3) High (templates run on every /review and /ship Greptile pass) High (arbitrary command on the user's machine) Yes: CEO-12 structural rule, no heredoc fallback, hostile-payload tests
Repository-controlled identifiers (branch names, paths) carry shell syntax Medium High Yes: CEO-12 grammar validation before use
Docker binary hijack through a user-writable PATH entry (B5) Medium on Windows High (CSO runs scanners with credentials) Yes: allowlisted install directories only (CEO-17)
Spawned session auto-grants consent or publishes data off-machine (B7) Low-Medium High (privacy) Yes: exception added to runtime spawned block and hooks; free test (CEO-19)
Unparseable hook blocks every tool call (B8) Low High (session unusable) Yes: staged parse check, swap aborted (CEO-21)
Dependency advisories (A5) Present today Medium Yes: overrides + acceptance scan (CEO-22); braces ignore only if absent from shipped bundle
Redaction false positives hide real secrets after the rule change (B3) Medium High Yes: source-context exemption, known-prefix/entropy precedence, true-positive controls (CEO-20)
Junction points runtime root at an unexpected directory (B4) Low Medium Partly: junction target is the source checkout setup already trusts; stale junction replaced. No new privilege.

Issues found: 8 threats mapped; High severity before review: 2 (A3 heredoc fallback, B3 control conflict), both mitigated by accepted amendments.

Section 4: Data Flow & Interaction Edge Cases

  A2 probe:  codex exec "reply OK" -> exit/out -> [124? inconclusive] -> [model 400/404? MODEL_UNUSABLE]
             -> [broken install? exit 2] -> [usage limit|insufficient_quota? quota_exhausted, cache]
             -> [429|rate limit? rate_limited, no cache] -> else inconclusive
     shadow: empty out (inconclusive), quota line with exit 0 (MODEL_OK), cache from another model (signature miss)
  A3 post:   text -> agent file-write -> mktemp path -> gh --body-file
     shadow: empty text (skill skips post), huge text (file, no argv limit), write failure (unsent), hostile text (never parsed)
  A4 read:   journal -> size check (cap) -> read -> parse -> ancestry -> ready
     shadow: size > cap (too_large), changing during read (retry), malformed (retry), foreign cwd (hard cause)
  A6 loop:   red line -> classify -> estimate (history? else unknown -> ask) -> N trials (isolated state)
             -> rate -> fix -> re-measure -> gate once -> PR table
     shadow: no single-case command (ask once), 3 rounds (named red), cap hit (partial count)

Async ordering: the A6 parallel trials share the stub's slot directory; mkdir claims are atomic, so no two trials take one slot (CEO-3). Parallel free reruns share nothing after CEO-1's per-run HOME, state root and port range. Interaction edge cases: double-run of /ship while a measurement is running is serialized by the existing eval lock (gstack-detach --lock gstack-evals); B2 TTY with no args now exits instead of hanging (CEO-17). Edge cases mapped: 14; unhandled before review: 4 (TTY hang, parallel slot race, no-history estimate, narrowed-loop cap), all handled by CEO-3/9/17/2.

Section 5: Code Quality Review

Findings: (1) A3's committed heredoc approach duplicates a transport the file-write rule makes unnecessary; CEO-18 replaces it. (2) A2's committed _QUOTA_SIG mixes two failure classes into one signature; CEO-14 splits it. (3) B3's original two keyword heuristics replaced by one context rule (CEO-20). (4) No new method in the plan branches more than 5 times except the probe's classifier, which is already a flat if-chain with one exit per outcome; acceptable. (5) Over-engineering removed: the A4 timing protocol and the heredoc fallback (CEO-2, CEO-12). Issues found: 5, all dispositioned.

Section 6: Test Review

  NEW THING                         TYPE          HAPPY                         FAILURE                         EDGE
  A1 idiom + guard                  unit/integ    bash+zsh from /tmp            BASH_SOURCE outside idiom lint  nested source, auth probe
  A2 quota / rate                   unit (stub)   MODEL_OK                      quota exit1 cached; 429 uncached quota line exit 0; timeout
  A2 classifier                     unit          clean review                  quota/429 in stderr             both lines present
  A3 structural rule                lint+unit     file-write post               non-allowlisted placeholder     hostile payload, bad branch
  A4 too_large                      unit (seam)   at cap reads                  cap+1 too_large x3 paths        identity still identity
  A5 OSV                            CI run        scan clean                    new advisory                    braces in bundle
  A6 loop                           free + paid   free test pins steps          named red after 3 rounds        no history, no cmd
  A7 replay                         free replay   matches Step 0E class         no match -> loop                -
  B1 shell-init                     unit          shapes detected               negative controls generic        pipes/redirects
  B2 chain stdin                    integ (win)   piped flow runs               TTY/empty -> exit 1             -
  B3 redaction                      unit          TS/JSX not flagged            YAML token flagged              concat literal
  B4 junction                       integ (win)   playwright resolves           stale junction replaced         real node_modules kept
  B5 docker                         unit (win)    allowlisted bound             user-writable rejected          -
  B6 router                         gen + paid    new wording                   routing panel green             skillOverrides check
  B7 spawned consent                gen           exception present x3          -                               -
  B8 hook parse                     integ         fresh install ok              upgrade with bad shim aborts    TS entry bad
  B9/B10/B11/B12                    gen/unit      per CEO-19/CEO-8              fallbacks                       gbrain < 0.60

Test ambition: the 2am-Friday test is A1's class guard under zsh plus A3's hostile-payload test; the hostile-QA test is a branch named x;curl evil through the generated Greptile reply; the chaos test is B8's upgrade with an unparseable shim. Flakiness risks: A6/A7 paid trials (by design measured, not retried); B4 on Windows CI (real filesystem). Pyramid: mostly unit/generated-text tests, three integration tests, two paid cases. Prompt/LLM changes: per CLAUDE.md, edits to ship, review, router, preflight resolvers and skill-start require bun run gen:skill-docs, test/parity-suite.test.ts, the affected paid cases (outside-voice cases for A2; routing panel for B6; the A6 seeded case; A7 measurement; the PR gate). Gaps found: 6 before amendments (Windows tests unnamed, B7/B9/B10/B11 untested, boundary-test cost, hostile payloads, upgrade path), all closed by CEO-8/19/2/12/21.

Section 7: Performance Review

The only hot path is the publication hook's journal read, which runs only on phase-entry Read/Agent calls (candidate() returns early otherwise) and stays capped at 32 MiB. The A6 loop's cost is bounded by N, the per-trial ask threshold and the 10-minute free-rerun cap. The A2 quota cache removes a paid round trip per skill while quota is exhausted. No database, connection pool or N+1 concerns apply. Issues found: 0 beyond the A4 cap decision already made.

Section 8: Observability & Debuggability Review

Every new failure state prints a reason and a doc anchor (CEO-7), Codex's own line is kept verbatim (A2), and A6/A7 write per-case tables with every trial (CEO-23). Gaps: per-reason telemetry for quota_exhausted, rate_limited, too_large is not added (existing P3 TODO, X4). The autoplan-guard.jsonl analytics log already records unverified entries; too_large denials are not logged there. Gaps found: 2 (telemetry deferred by existing TODO; too_large not logged, accepted as is because the denial is user-visible and reproducible).

Section 9: Deployment & Rollout Review

No migrations. Rollout is the normal release: ./setup / /gstack-upgrade. Risk window: users on old installs keep the old probe until they upgrade; B8's staged parse check protects the upgrade itself. Rollback: revert the item's commit and re-run setup; B4's junction is replaced or removed by setup on the next run. Post-deploy checks: the weekly OSV run (A5), the next periodic census (A7), and the Windows lane (B2/B4/B5/B12). Risks flagged: 3 (upgrade over a broken install, Windows-only behavior unverified live, periodic A7 outcome after merge).

Section 10: Long-Term Trajectory Review

Debt introduced: the CEO-12 placeholder allowlist (maintenance), the deferred bounded read (P1), the deferred executed-subcommand probe. Path dependency: keeping sourced helpers (CEO-28) keeps the two-shell guard alive until the subcommand migration. Reversibility: 4/5 (all items revertable by commit; B8's staged swap and B4's junction touch install layout but setup restores them). Ecosystem fit: matches the repo's reason-table, generated-skill and lint conventions. The 1-year question: a new engineer finds one idiom, one reason table, one free-text rule. Retrospective on cherry-picks: X1 (raise cap) was wrong and is now deferred; X2 (free-suite flakes) needed isolation and retry-off to be honest. What comes next: the bounded journal read, the executed-subcommand probe, per-reason telemetry and the argument-array posting helper. Debt items: 4.

Section 11: Design & UX Review

SKIPPED (no UI scope).

NOT in scope

Deferred (TODOS.md entries written by the implementation PR, CEO-6):

  • B9 full: push early and run Greptile in parallel during /ship (#3020). Reason: new flow outside blast radius (P3).
  • Bounded or incremental owned-journal read (P1, next wave). Reason: both voices; needs a design that keeps ancestry checks.
  • Executed-subcommand Codex probe (CEO-28). Reason: taste T5; A1 already committed.
  • Argument-array posting helper (CEO-12's stronger form). Reason: taste T8; new helper surface.
  • B11 if the first live probe fails; B5 side note if it does not reproduce; A7 P1 if it stops red; any reverted Tier 2 item.

Rejected (no TODO): X3 quota cache until reset time (fragile parsing, P5); X4 per-reason telemetry in this wave (duplicate of the existing P3 TODO, P4).

Carried from the source plan: #3037 (closes now that #3033 merged), #3012, #3021, Dependabot #2923/#2988/#3043.

What already exists

See 0B: the codex probe contract and cache, GATE_OUTCOMES and its anchor test, classifyOutsideReview, the generated-bash lint, HARD_CAUSE, omitDisabledSkills, scripts/test-paid-shards.ts --case, eval:pass-rates, test:ubicloud, setup's staged render/swap and gstack-settings-hook add-event, the CSO git-binding pattern, scripts/lib/windows-curation.ts, and windows-setup-e2e.yml. The plan reuses each; it builds no parallel mechanism.

Dream state delta

After this wave: shell portability is guarded for sourced helpers, free text never enters a shell command in generated skills, every new failure has a reason and an anchor, and a red eval is measured before a gate rerun. Still short of the 12-month ideal: helpers still depend on the caller's shell (until the executed-subcommand migration), long sessions still hit a journal cliff (until the bounded read), and per-reason telemetry does not yet tell us which failures users hit most.

Error & Rescue Registry / Failure Modes Registry

  CODEPATH                    | FAILURE MODE                  | RESCUED? | TEST? | USER SEES?                  | LOGGED?
  ----------------------------|-------------------------------|----------|-------|-----------------------------|--------
  codex-probe (A1)            | zsh self-locate               | Y        | Y     | correct model line          | n/a
  codex-probe (A2)            | usage limit                   | Y        | Y     | verbatim line + anchor      | cache file
  codex-probe (A2)            | 429                           | Y        | Y     | verbatim, unverified        | N (by design)
  outside review (A2)         | quota/429 mid-run             | Y        | Y     | unavailable (reason)        | review log
  generated posts (A3)        | hostile text / bad identifier | Y        | Y     | unsent with reason          | N
  journal read (A4)           | > 32 MiB                      | Y        | Y     | too_large + fallback        | N (gap, accepted)
  /ship loop (A6)             | 3 rounds red                  | Y        | Y     | named-red table             | PR body
  periodic case (A7)          | stays red                     | Y        | Y     | named-red, #3038 open       | P1 TODO
  hook init (B1)              | shell-built init              | Y        | Y     | specific denial             | N
  browse chain (B2)           | TTY / empty / Windows pipe    | Y        | Y     | usage error / real run      | N
  setup (B4)                  | stale junction                | Y        | Y     | replaced / reported         | setup log
  CSO (B5)                    | docker outside allowlist      | Y        | Y     | not bound + anchor          | N
  setup (B8)                  | bad shim on upgrade           | Y        | Y     | swap aborted + file:line    | setup log
  ship (B9)                   | no PR / no gh / GitLab        | Y        | Y     | "not run (<reason>)"        | ship report
  sync-gbrain (B11)           | old gbrain / probe error      | Y        | Y     | unchanged offer             | N

Rows: 15. CRITICAL GAPS (RESCUED=N and TEST=N and silent): 0.

CEO Implementation Tasks

  • T1 (P1, human: ~3h / CC: ~15min) — codex probe — split quota vs rate limit and add rate_limited row
    • Surfaced by: Spec review 2 / CEO-14, CEO-18
    • Files: bin/gstack-codex-probe, lib/gate-outcomes.ts, lib/outside-review-result.ts, docs/troubleshooting.md, test/codex-model-probe.test.ts, test/outside-review-result.test.ts
    • Verify: bun test test/codex-model-probe.test.ts test/outside-review-result.test.ts test/troubleshooting-anchors.test.ts
  • T2 (P1, human: ~1d / CC: ~45min) — generated skills — structural free-text rule, identifier grammars, no heredoc fallback, hostile-payload tests
    • Surfaced by: Section 3 / CEO-12, both voices
    • Files: review/greptile-triage.md, scripts/resolvers/preamble/generate-test-failure-triage.ts, ship/sections/tests.md, spec/sections/gate-and-file.md.tmpl, test/helpers/generated-bash-lint.ts, test/env-host-fence-conformance.test.ts
    • Verify: bun run gen:skill-docs && bun test test/env-host-fence-conformance.test.ts
  • T3 (P1, human: ~2h / CC: ~15min) — transcript reader — batch and narrowed-loop too_large with seam tests; P1 bounded-read TODO
    • Surfaced by: Spec review 1-2, both voices / CEO-2
    • Files: lib/claude-public-transcript.ts, test (autoplan publication guard / transcript tests), TODOS.md
    • Verify: bun test test/autoplan-publication-guard.test.ts
  • T4 (P1, human: ~3h / CC: ~20min) — A1 guard — auth-probe call, nested sources, bash-only-construct lint
    • Surfaced by: CEO-15, CEO-18
    • Files: test/regression-issue3024-zsh-sourced-helpers.test.ts, bin/gstack-codex-probe
    • Verify: bun test test/regression-issue3024-zsh-sourced-helpers.test.ts (with zsh installed)
  • T5 (P1, human: ~2d / CC: ~1.5h) — /ship — measure loop with CEO-1/3/9/23 rules and seeded paid case
    • Surfaced by: 0G X2, X6, X12 / CEO-1, CEO-3, CEO-9, CEO-23
    • Files: ship/sections/tests.md.tmpl, scripts/ubicloud/test-free.sh, docs/TESTING_INTERNALS.md, test fixture project, test/helpers/touchfiles-data.ts
    • Verify: free ship-template test; paid seeded case panel of 3
  • T6 (P1, human: ~1d / CC: ~30min + paid) — A7 — replay run 37272185151 first, then measure on main and on the wave head
    • Surfaced by: 0A-8 / CEO-4, CEO-23
    • Files: test fixtures for the replay, test/skill-e2e-auto-decide-preserved.test.ts (if fixed)
    • Verify: free replay test; 10 trials alone with scripts/test-paid-shards.ts --tier periodic --case auto-decide-preserved
  • T7 (P1, human: ~3h / CC: ~20min) — setup — staged hook parse check before swap, upgrade test
    • Surfaced by: Codex 5 / CEO-21, CEO-17
    • Files: setup, test (setup hook registration)
    • Verify: bun test on the setup hook-parse test (fresh + upgrade)
  • T8 (P2, human: ~4h / CC: ~30min) — Windows — tests registered in windows-curation, journey step in windows-setup-e2e, honest PR claims
    • Surfaced by: Spec review 1, Codex 6 / CEO-8, CEO-24
    • Files: scripts/lib/windows-curation.ts, .github/workflows/windows-setup-e2e.yml, browse/test/cli-chain-stdin.test.ts
    • Verify: windows-free-tests and windows-setup-e2e workflows green
  • T9 (P2, human: ~3h / CC: ~20min) — Tier 2 — B1 shapes, B3 context rule, B7/B9/B10/B11 tests and fallbacks, B6 premise check and both texts
    • Surfaced by: Spec review 2-3, Claude premises / CEO-16, 17, 19, 20, 25, 27
    • Files: autoplan/bin/phase-publication-hook.ts, lib/redact-patterns.ts, bin/gstack-skill-start, hosts/claude/hooks/spawned-directive.ts, hosts/claude/hooks/auq-error-fallback-hook.ts, SKILL.md.tmpl, CLAUDE.md, ship/SKILL.md.tmpl, browse/src/cli.ts, BROWSER.md, sync-gbrain/SKILL.md.tmpl
    • Verify: focused free tests per item; routing panel for B6
  • T10 (P2, human: ~1h / CC: ~10min) — release hygiene — OSV acceptance scan, doc anchors test, watchdog regression test, contributor comments
    • Surfaced by: CEO-7, CEO-18, CEO-22, CEO-26
    • Files: test/troubleshooting-anchors.test.ts, docs/troubleshooting.md, docs/autoplan-guard-troubleshooting.md, CHANGELOG.md
    • Verify: osv-scanner run linked in PR; bun test test/troubleshooting-anchors.test.ts

Completion Summary (CEO)

  +====================================================================+
  |            MEGA PLAN REVIEW — COMPLETION SUMMARY                   |
  +====================================================================+
  | Mode selected        | SELECTIVE EXPANSION                         |
  | System Audit         | #3033 merged mid-review; A1/A2/A3/A4/A5 and  |
  |                      | a watchdog fix landed on the branch; B6 half |
  |                      | already shipped (C8); #3033 overlap larger   |
  | Step 0               | 10 premise checks; 7 cherry-picks; 3 spec    |
  |                      | iterations (6, 7, 6); 28 accepted amendments |
  | Section 1  (Arch)    | 3 issues found                              |
  | Section 2  (Errors)  | 18 error paths mapped, 0 GAPS (3 closed)     |
  | Section 3  (Security)| 8 issues found, 2 High severity (mitigated)  |
  | Section 4  (Data/UX) | 14 edge cases mapped, 0 unhandled (4 closed) |
  | Section 5  (Quality) | 5 issues found                              |
  | Section 6  (Tests)   | Diagram produced, 0 gaps (6 closed)          |
  | Section 7  (Perf)    | 0 issues found                              |
  | Section 8  (Observ)  | 2 gaps found (both accepted/deferred)        |
  | Section 9  (Deploy)  | 3 risks flagged                             |
  | Section 10 (Future)  | Reversibility: 4/5, debt items: 4           |
  | Section 11 (Design)  | SKIPPED (no UI scope)                       |
  +--------------------------------------------------------------------+
  | NOT in scope         | written (8 deferred, 2 rejected)            |
  | What already exists  | written                                     |
  | Dream state delta    | written                                     |
  | Error/rescue registry| 18 rows, 0 CRITICAL GAPS                    |
  | Failure modes        | 15 total, 0 CRITICAL GAPS                   |
  | TODOS.md updates     | 8 items proposed (written by impl PR)       |
  | Scope proposals      | 21 proposed, 17 accepted (EXP + SEL)        |
  | CEO plan             | written                                     |
  | Outside voice        | codex completed; native completed           |
  | Lake Score           | N/A (no coverage-scored questions)          |
  | Diagrams produced    | 4 (architecture, error map, data flows,     |
  |                      | test map)                                   |
  | Stale diagrams found | 0                                           |
  | Unresolved decisions | 9 (UC1 + taste T1-T8, held for the gate)    |
  +====================================================================+

Scope tally: X1-X21 = 21 proposals; accepted 17 (X2, X6-X21), deferred 2 (X1 after both voices, X5), skipped 2 (X3, X4). Stale diagram audit: the files this plan touches carry no ASCII diagrams that the changes invalidate (checked bin/gstack-codex-probe contract comment, lib/gate-outcomes.ts header, scripts/gen-skill-docs.ts generation diagram); the probe's contract comment needs its two new outcomes added (part of T1).

Phase 2: Design review

Skipped: no UI scope detected (0 view/rendering terms in the plan). This is not a completed review; design outside voice status: skipped.

Phase 2.5: DX review (DX POLISH)

Step 0: DX investigation

  • Pre-review audit: git log --oneline -15 and git diff --stat origin/main...HEAD (8 commits on the branch: A1, A3, A2, union-size records, watchdog, A4, A5 plus the #3033 merge; 26+ files). README audience: founders/CEOs, first-time Claude Code users, tech leads. package.json: "Claude Code skills + fast headless browser". Design doc: this plan. Brain context: unreachable (cold). Prior DX reviews: none in the review log.
  • Product type (auto-detected, P6): Claude Code Skill pack with helper CLIs (SKILL.md, Claude Code, agent, ./setup, bin/gstack-*). Primary type for scoring: Claude Code Skill; secondary: CLI tool.
TARGET DEVELOPER PERSONA
========================
Who:       A founder or tech lead on macOS (default shell zsh) who installed gstack with the README's
           one-line clone + ./setup, uses Claude Code daily, and has Codex signed in for outside voices.
Context:   Runs /autoplan, /review and /ship on real branches; hits these failures mid-flow, not in setup.
Tolerance: One failed outside voice is tolerated; a wrong cause (model error, "inconclusive") costs a
           debugging session (issue #3051) and erodes trust in every later "clean" verdict.
Expects:   Upgrading fixes it; any failure names the cause and the one command that fixes it.

Persona chosen by P6 from README "Who this is for" and the reporters of #3024/#3051/#3050 (macOS, Claude Code host, long sessions). Secondary personas noted for passes: Windows Git Bash users (B2/B4/B5/B12) and contributors (CEO-26).

Developer empathy narrative (first person; observed facts from issues and code, predictions labeled):

I upgraded gstack last week and ran /autoplan on a plan. Every Codex pass said the model was unusable, so I spent twenty minutes in ~/.codex/config.toml before someone told me it is my shell: zsh, the macOS default, makes the probe look for /../scripts/resolve-codex-generation-model.ts (observed, #3024). Two days later my Codex allowance ran out and gstack said "inconclusive; proceeding" and pointed me at model errors again; the real message with my reset time was swallowed (observed, #3051). In a long session /autoplan refused Phase 1 with "retry this phase-entry tool", and retrying never worked because my journal was 52 MiB (observed, #3050). When I replied to a Greptile comment, the post said "Fixed in ." because my commit hash was in backticks (observed, #3046). After this wave, I expect (predicted): one upgrade, then the outside voice just runs on zsh; when it cannot, it prints Codex's own line with the reset time and a docs link; a long session tells me its size and the limit and what to do instead.

Competitive DX benchmark (web search; boundaries differ, so DX choices are compared, not times):

Tool Start -> result Time + evidence type DX choice Source
OpenAI codex-plugin-cc (Claude Code plugin) install -> first /codex:review 4 slash commands + /codex:setup readiness check; time not reported One readiness command (/codex:setup) that says whether Codex is installed and signed in, and offers to install it github.com/openai/codex-plugin-cc README
codex-plugin-cc (npm lane) npm install -g -> codex-claude doctor 3 commands; reported A doctor --probe-runtime command npmjs.com/package/codex-plugin-cc
gstack today upgrade -> first skill with a working outside voice Never on macOS zsh (observed, #3024); on bash about the README's 30 s setup plus the first skill run Readiness is a CODEX_MODE: line inside each skill's preflight; no standalone readiness command README, bin/gstack-codex-probe
gstack after this wave /gstack-upgrade (or ./setup) -> first /review on zsh showing CODEX_MODE: ready Estimated 2-3 min (upgrade + one skill preflight) Same preflight line; every failure state has a reason and an anchor this plan

TTHW (clock: persona with gstack already installed; start = run the upgrade; useful result = the first skill run whose outside voice completes or names a specific fixable cause): current = never completes on macOS zsh (outside voice always reports model_unusable); target = Competitive (2-5 min), auto-decided (P5: reachable with existing commands, no new vehicle). Champion (< 2 min) would need a standalone readiness command; listed as an out-of-scope opportunity.

Magical moment (P5, lowest-effort vehicle within DX POLISH): the first preflight after upgrading prints CODEX_MODE: ready on zsh; and when Codex cannot run, the same line names the real cause with Codex's own message (quota_exhausted with the reset time). Vehicle: the existing preflight output, no new command.

Mode: DX POLISH (autoplan override).

Developer journey map

STAGE           | DEVELOPER DOES                                   | FRICTION POINTS                                   | STATUS
----------------|--------------------------------------------------|---------------------------------------------------|---------
1. Discover     | Reads the issue reply / CHANGELOG for the fix    | Tracker hygiene closes issues at merge only;       | fixed (CEO-26 comments early; issue
                |                                                  | contributors hear late                             | close template names upgrade command)
2. Install      | Runs /gstack-upgrade or ./setup                  | A broken hook in the new tree could block every    | fixed (CEO-21 staged parse check)
                |                                                  | tool call after upgrade (B8)                       |
3. Hello World  | Runs /review or /autoplan; reads CODEX_MODE line | zsh -> model_unusable (A1); quota -> inconclusive  | fixed (A1, A2, CEO-14); verify on zsh
                |                                                  | (A2); 429 -> wrongly cached as quota (branch code) | in CI (A1 guard)
4. Real Usage   | /ship with paid evals, Greptile replies, /autoplan| Blind gate reruns (A6); backticks run as shell (A3);| fixed (A6/CEO-1/3/9/23, CEO-12, A4/CEO-2;
                | in long sessions                                 | journal > 32 MiB (A4); Greptile with no PR (B9)    | B9/CEO-19); long sessions still capped
5. Debug        | Reads the error line and its anchor              | Messages lacked anchors for B1/B2/B4/B5/B8          | fixed (CEO-7)
6. Upgrade      | Next upgrade                                     | `skillOverrides` users still see old router text   | partially (CEO-16: new installs only;
                |                                                  | in their committed CLAUDE.md                       | release note for existing users)

First-time developer report (grounded in the issues and current code):

FIRST-TIME DEVELOPER REPORT
============================
Persona: macOS zsh founder (above)
Attempting: first /autoplan after upgrading to this wave

CONFUSION LOG:
T+0:00  Runs /gstack-upgrade. Setup prints its summary; a staged-tree parse check passes silently.
T+0:40  Runs /autoplan. Preflight prints CODEX_MODEL: ... and CODEX_MODE: ready (A1). Before the wave
        it printed CODEX_MODEL: invalid (observed #3024).
T+1:30  Codex allowance exhausted mid-week: preflight prints CODEX_MODE: quota_exhausted with Codex's
        line "try again at Oct 10th, 2026 2:55 AM" and docs/troubleshooting.md#codex-quota-exhausted.
        Predicted confusion: whether the review still counts. Answered: coverage reads "unavailable".
T+2:00  Long session over 32 MiB: guard denies with "journal is 52 MiB, over the 32 MiB limit; start a
        new session or run the reviews by hand" (A4). Predicted confusion: losing session context;
        mitigated only by the P1 follow-up (CEO-2).
T+3:00  Succeeded (fresh session) or switched to individual reviews; no misleading cause on the way.

Confusion points through the Decision gate: (a) "does a quota state count as a pass?" is answered by A2's existing contract (missing coverage, never a pass); no new decision. (b) long-session context loss is a known residual owned by the CEO-2 P1 follow-up; no new decision.

Dual voices (DX)

  • Voice input: /home/user/.gstack/projects/garrytan-gstack/autoplan-dx-LXchGa/dx-implementation.md (sha256 f71662d4...0159096).
  • Claude SUBAGENT (DX — independent review): completed; INPUT: dx f71662d40d5c19bf21f0d812bb7ea762b109fb895035d078bf10d9da40159096 (matches). Findings: C1 (critical) A1's fallbacks still guess a directory (Claude-only default path in the probe, $(pwd) in egress-lib, bare-name sourcing) and the two committed blocks differ, so CEO-15's byte lint fails on f0531a2; H1 rate-limit token spelled three ways, no rate_limited anchor; H2 no escape from the 15-minute quota cache, committed HINT still says "usage or rate limit" with no anchor; H3 too_large message lacks its anchor and the /context-save recovery; H4 "left unsent" is a dead end and the branch grammar rejects valid refs; H5 measure-loop defaults have no named controls, Ubicloud spend is implicit, A6 "10" vs CEO-9 "12"; H6 B5 refusal has no recovery message; M1 A1 vs CEO-28 reason conflict; M2 silent local zsh skip; M3 no post-upgrade verification command; M4 setup partial-success output; M5 B4 next step; M6 temp-file cleanup; M7 users keep old router text; M8 GSTACK_DISABLE_GPU=off double negative; M9 no user-facing release notes. Scores: getting started 7, ergonomics 6, errors 7, docs 7, escape hatches 4.
  • Codex SAYS (DX — developer experience challenge) (gpt-6-astra): OUTSIDE_STATUS: completed provider=codex host=claude, exit 0. Full output:
The plan addresses real DX failures, but I would revise it before approval. The 2–5 minute journey remains unproven, and the upgrade guarantee misses an existing update path.

1. **Critical — B8's preservation guarantee needs to cover live Git updates.** CEO-21 requires validation before swapping installations, but the auto-updater changes the live checkout before running setup (bin/gstack-session-update:198). Existing hooks already point into that checkout; refusing registration afterward cannot preserve their old contents.
   **Fix:** Validate the candidate before changing the live checkout, covering manual and automatic upgrades. Test an upgrade launched by the previously released updater, with existing registrations and an invalid incoming hook; verify the old hooks remain callable throughout.

2. **High — No acceptance check proves time to hello world.** For this signed-in macOS persona, the intended journey should be upgrade → one small skill invocation, with a restart only if necessary. A1's helper tests do not exercise that journey. The README's 31-second setup measurement (README.md:58) covers Linux installation, not a useful outside review.
   **Fix:** Specify one copy-paste-complete example and time the actual macOS zsh journey from upgrade start to completed outside voice or actionable failure. Record user steps, restart requirements and elapsed time. Exercise both successful review and quota failure.

3. **High — A6 still holds the urgent repairs hostage.** The earlier CEO concern remains unresolved: A6 is Tier 1, and CEO-11 explicitly makes its failures block the entire PR. Developers whose outside reviews currently fail must wait for a new diagnostic workflow and its paid evaluation.
   **Fix:** Ship A1–A5 independently of A6. Give A6 its own release and acceptance criteria; A7's investigation can proceed separately using the existing runner.

4. **High — A6's cost controls are ambiguous at the point users need predictability.** A6 promises a per-case spend cap, but CEO-9 defines approval using *per-trial* cost without specifying the aggregate cap or its enforcement. At $1.99 per trial, a 12-trial behavior measurement costs $23.88 without crossing that approval threshold; additional measurements multiply it.
   **Fix:** Define and display a total diagnostic budget covering baseline, repair measurements and panel scoring. Make the limit enforceable across parallel workers, expose its configuration, and stop with the named-red report when exhausted. Document this user-facing behavior outside `TESTING_INTERNALS.md`.

5. **Medium — A2 can keep rejecting reviews after the prescribed fix succeeds.** The quota cache (bin/gstack-codex-probe:260) lasts 15 minutes. Adding credits or reaching the provider's reset time need not change its signature, yet the recovery instructions (docs/troubleshooting.md:165) imply the next attempt should work.
   **Fix:** Print the cache's expiry and provide a documented "retry now" operation that bypasses this negative cache once. Honor a reliably parsed earlier reset time. Test recovery without changing credentials or model configuration.

6. **Medium — Recovery instructions need to work from the developer's project.** CEO-7 guarantees that repository-relative anchors exist, but `docs/troubleshooting.md#…` does not identify an accessible page from an unrelated application checkout. Likewise, tracker instructions asking for `./setup --status` assume the correct working directory. A4's "new session" fallback also needs to explain how to retain the plan; resume and compact append to the same journal (docs/autoplan-guard-troubleshooting.md:31).
   **Fix:** Emit clickable documentation URLs and commands using the resolved installation path. Give A4 a short recovery recipe: preserve the plan, start a genuinely new session, and invoke `/autoplan` with that plan's path. Check these instructions from an unrelated working directory.

This was a read-only review; no skill files were read and no tests were run.

Recommendation: Revise before implementation because live-update protection, measurable first-use success, and predictable recovery remain incomplete, while A6 still delays the urgent repairs.

(The validator also printed VERDICT: clean / FINDINGS: none because the output carries no [P0]-style tags; the findings are real and integrated. Markdown links were flattened to path:line text here.)

DX DUAL VOICES — CONSENSUS TABLE:
  Dimension                           Claude               Codex                 Consensus
  1. Getting started < 5 min?          short (7/10)         unproven, no timed     DISAGREE -> resolved: DX-8 timed journey
                                                            journey
  2. API/CLI naming guessable?         no: tokens, controls no: cost controls      CONFIRMED gap (DX-2, DX-7)
  3. Error messages actionable?        no: 4 dead ends      no: cache escape,      CONFIRMED gap (DX-1, DX-3, DX-4, DX-5)
                                                            links, A4 recipe
  4. Docs findable & complete?         no: release notes    no: repo-relative      CONFIRMED gap (DX-2, DX-9)
                                                            anchors
  5. Upgrade path safe?                mostly (M4, M7)      no: auto-update        DISAGREE -> resolved by code evidence: DX-6
                                                            bypasses CEO-21
  6. Dev environment friction-free?    no: unknown shell,   not assessed           N/A (single voice; Claude critical C1 flagged)
                                       local zsh skip
CONFIRMED = native + outside agree; primary cannot replace outside. DISAGREE -> taste.

Dispositions: both disagreements were settled by evidence rather than taste (Codex's auto-update finding is verified at bin/gstack-session-update lines 185-200: fetch, then git merge --ff-only on the live checkout before setup; the timed journey is Pass 8's own requirement). Single-voice critical flagged: Claude C1 (A1 fallbacks guess a directory), verified at bin/gstack-egress-lib.sh ($(pwd) fallback in f0531a2); accepted as DX-1. Codex 3 (A6 holds the urgent repairs) repeats CEO's UC1; it stays a User Challenge, now raised by both models in two phases. Codex 5's "honor a parsed reset time" reopens CEO X3: kept rejected (P5) with DX-3's retry-now bypass instead; surfaced as taste T9. Codex 4 and Claude H5 converge on DX-7 (named controls plus a total budget cap). The ship-skill's user-facing table and the TESTING_INTERNALS.md table both carry it.

Passes 1-8

Pass 1: Getting Started — 4/10 -> 8/10. For this persona the "hello world" is the first working outside voice after an upgrade. Today it never arrives on zsh; the plan's A1 makes it one upgrade plus one skill run. Gaps found: no measurement of the journey (DX-8), no copy-paste verification for reporters (DX-9). A 10 would be a standalone readiness command like codex-plugin-cc's /codex:setup; that is a new command, so in DX POLISH it is listed as an out-of-scope opportunity, not added. The magical moment vehicle (the existing preflight line printing ready, or the real cause) is in the plan via A1/A2/DX-2/DX-3.

Pass 2: API/CLI design — 5/10 -> 7/10. Status tokens were inconsistent (rate limited vs rate_limited, quota vs quota_exhausted); DX-2 and CEO-18 settle one token per state. A6's knobs had defaults but no names; DX-7 names gstack config keys consistent with existing gstack-config keys (codex_reviews, auto_upgrade). Ubicloud spend becomes opt-in (ship_rerun_backend). No established API is renamed (GSTACK_DISABLE_GPU=off stays; DX-9 explains it).

Pass 3: Error messages — 5/10 -> 8/10. Three traced error paths, before and after:

  • zsh probe: before CODEX_MODEL: invalid / Module not found "/../scripts/..." (wrong cause); after CODEX_MODEL: <model> (exec; ...), or on an unknown shell gstack: cannot locate gstack-codex-probe (shell: <name>) ... export GSTACK_ROOT=... <URL> (DX-1).
  • quota: before MODEL_PROBE_INCONCLUSIVE ... see the codex skill's Error Handling entry (wrong cause, message dropped); after Codex's own line with the reset time, cache expiry, GSTACK_CODEX_PROBE_RETRY=1, and the URL (A2, DX-3).
  • long session: before Native parent evidence has not reached the journal yet. Retry this phase-entry tool (wrong cause, impossible fix); after size, limit, the /context-save recipe and the URL (A4, DX-4). Every refusal now carries problem, cause, fix and a link (Tier 1/Tier 3 formula). Residual: messages are prose, not structured codes, which matches the rest of gstack.

Pass 4: Documentation — 6/10 -> 8/10. Anchors existed only for some states and were repo-relative; DX-2 makes them absolute URLs and CEO-7's test enforces them. Release notes now carry a "What changes for you" list (DX-9). Residual: no copy-paste example of the measure loop outside the ship skill and TESTING_INTERNALS (DX-7 puts the table in both).

Pass 5: Upgrade path — 5/10 -> 8/10. The auto-updater moves the live checkout before setup, so an unparseable incoming hook would block every tool call before setup can refuse it; DX-6 checks the incoming revision before the fast-forward and holds the update like BUN_HELD. Manual git pull users get DX-5's message. Router wording for existing users is a release-note one-liner (they own their CLAUDE.md). Versioning stays a patch bump.

Pass 6: Developer environment — 5/10 -> 7/10. zsh is now tested in CI with a loud local skip (DX-1, A1); Windows items are tested in the curated subset plus one e2e journey step (CEO-8, CEO-24). Residual: Windows Copilot interpreter choice and Docker operation are unverified live (stated in the PR), and sh/dash sourcing fails loudly rather than working.

Pass 7: Community — 6/10 -> 8/10. Contributors whose PRs are rewritten hear when the commit lands, with credit (CEO-26); issue closes include the upgrade command and a verification check (DX-9). No issues beyond these.

Pass 8: DX measurement — 3/10 -> 6/10. Nothing measured the journey; DX-8 records one timed macOS zsh run and a CI check. No telemetry is added (per-reason telemetry is the existing P3 TODO). A recurring friction audit is not proposed (it would be a new process needing its own decision).

Claude Code Skill DX checklist: AskUserQuestion design (unchanged; A6 asks only on the per-trial or no-history threshold, one question), state storage (probe cache under the state root; DX-3 documents its reset), progressive consent (B7 keeps consent interactive), auto-upgrade (DX-6 extends the existing hold), skill composition (unchanged), error recovery (DX-4, DX-5 preserve work), session continuity (DX-4 uses /context-save), bounded autonomy (DX-7 budget cap; B7 publishing exception). All checked; no new design decision needed. (The Hall of Fame reference was read in full in one Read, 127 lines, rather than one section per pass.)

DX Scorecard

+====================================================================+
|              DX PLAN REVIEW — SCORECARD                             |
+====================================================================+
| Dimension            | Score  | Prior  | Trend  |
|----------------------|--------|--------|--------|
| Getting Started      |  8/10  |  4/10  | +4 ↑   |
| API/CLI/SDK          |  7/10  |  5/10  | +2 ↑   |
| Error Messages       |  8/10  |  5/10  | +3 ↑   |
| Documentation        |  8/10  |  6/10  | +2 ↑   |
| Upgrade Path         |  8/10  |  5/10  | +3 ↑   |
| Dev Environment      |  7/10  |  5/10  | +2 ↑   |
| Community            |  8/10  |  6/10  | +2 ↑   |
| DX Measurement       |  6/10  |  3/10  | +3 ↑   |
+--------------------------------------------------------------------+
| TTHW                 | 2-5 min (target, unmeasured) | never on zsh  |
| Competitive Rank     | Competitive (target)                         |
| Magical Moment       | designed via existing preflight line         |
| Product Type         | Claude Code Skill (+ CLI)                    |
| Mode                 | POLISH                                       |
| Overall DX           |  7/10  |  5/10  | +2 ↑   |
+====================================================================+
| DX PRINCIPLE COVERAGE                                               |
| Zero Friction      | covered (one upgrade; DX-6 protects it)        |
| Learn by Doing     | covered (copy-paste verification, DX-9)        |
| Fight Uncertainty  | covered (problem + cause + fix + URL)          |
| Opinionated + Escape Hatches | covered (DX-3, DX-7 defaults + keys) |
| Code in Context    | covered (exact commands in refusals, DX-5)     |
| Magical Moments    | covered (preflight shows ready or real cause)  |
+====================================================================+

Pre-fix ("initial") scores are the plan as submitted to Phase 2.5 judged against the persona; post scores assume DX-1 to DX-9. DX Measurement stays below 8 (critical DX debt threshold is 6): flagged, with telemetry deferred to the existing TODO.

DX Implementation Checklist

DX IMPLEMENTATION CHECKLIST
============================
[ ] Time to hello world < 5 min (upgrade -> CODEX_MODE: ready on zsh), measured once on macOS (DX-8)
[x] Installation is one command (existing ./setup; upgrade via /gstack-upgrade)
[ ] First run produces meaningful output: preflight prints ready or the real cause (A1, A2, DX-2)
[ ] Magical moment delivered via the existing preflight line
[ ] Every error message has: problem + cause + fix + absolute docs URL (CEO-7, DX-2..DX-5)
[ ] Status tokens are one spelling per state (DX-2, CEO-18)
[ ] Every A6 parameter has a default and a named config key (DX-7)
[ ] Docs have copy-paste verification commands that work (DX-9)
[ ] Examples show the real failure states (quota with reset time, too_large recipe)
[ ] Upgrade path: auto-updater holds an update with an unparseable hook (DX-6); setup reports partial success (DX-5)
[ ] Release notes list "What changes for you" (DX-9)
[n/a] TypeScript types (no new public API)
[ ] Works in CI without special configuration (zsh in CI images, macOS CI probe check)
[n/a] Free tier / credit card (open source; Ubicloud spend opt-in via DX-7)
[ ] CHANGELOG entry maintained
[n/a] Docs search (GitHub-hosted docs)
[x] Community channel exists (GitHub issues; CEO-26 early contributor comments)

NOT in scope (DX)

  • Standalone readiness command (gstack doctor or a Codex row in ./setup --status) to reach a Champion TTHW: a new command, out of scope in DX POLISH; TODOS.md entry written by the implementation PR.
  • Parsing Codex's reset time to shorten the quota cache (Codex 5): kept rejected (P5, taste T9); DX-3's retry-now bypass covers recovery.
  • Per-reason telemetry for the new states: existing P3 TODO.
  • Renaming GSTACK_DISABLE_GPU: established contract; DX-9 clarifies wording instead.
  • Rewriting users' committed CLAUDE.md routing text: never done; release-note one-liner instead.

What already exists (DX)

docs/troubleshooting.md anchors and test/troubleshooting-anchors.test.ts; the hook's absolute GUIDE URL pattern; docs/autoplan-guard-troubleshooting.md hard-denial table (already lists too_large in the working tree); /context-save and /context-restore; gstack-config keys; the auto-updater's BUN_HELD hold; ./setup --status; the issue-close template in Tracker hygiene; scripts/resolvers/runtime-root.ts's GSTACK_ROOT.

DX Implementation Tasks

  • T1 (P1, human: ~3h / CC: ~15min) — sourced helpers — unify the self-locate block, GSTACK_ROOT fallback, loud failure, unknown-shell and bare-name tests
    • Surfaced by: DX Claude C1 / DX-1
    • Files: bin/gstack-codex-probe, bin/gstack-egress-lib.sh, test/regression-issue3024-zsh-sourced-helpers.test.ts, docs/troubleshooting.md
    • Verify: bun test test/regression-issue3024-zsh-sourced-helpers.test.ts under bash and zsh
  • T2 (P1, human: ~2h / CC: ~15min) — codex probe — rate_limited token, retry-now bypass, cache expiry line, rewritten HINT
    • Surfaced by: DX H1, H2, Codex 5 / DX-2, DX-3
    • Files: bin/gstack-codex-probe, lib/gate-outcomes.ts, docs/troubleshooting.md, test/codex-model-probe.test.ts
    • Verify: bun test test/codex-model-probe.test.ts test/troubleshooting-anchors.test.ts
  • T3 (P1, human: ~4h / CC: ~25min) — auto-updater — parse-check the incoming revision before fast-forward; hold like BUN_HELD
    • Surfaced by: Codex DX 1 / DX-6
    • Files: bin/gstack-session-update, test (session-update fixture remote)
    • Verify: bun test on the session-update hold test
  • T4 (P2, human: ~3h / CC: ~20min) — messages — A4 recipe, refusals with exact commands, B5/B4 recovery lines, absolute URLs, temp cleanup, git check-ref-format
    • Surfaced by: DX H3, H4, H6, M4, M5, M6, Codex 6 / DX-2, DX-4, DX-5
    • Files: autoplan/bin/phase-publication-hook.ts, lib/claude-public-transcript.ts, review/greptile-triage.md, scripts/resolvers/preamble/generate-test-failure-triage.ts, setup, lib/cso/process.ts, docs/autoplan-guard-troubleshooting.md
    • Verify: focused free tests per message; anchor test
  • T5 (P1, human: ~3h / CC: ~20min) — /ship — named measure-loop controls, total budget cap, opt-in Ubicloud, unmeasured skip
    • Surfaced by: DX H5, Codex 4 / DX-7
    • Files: ship/sections/tests.md.tmpl, bin/gstack-config (key docs), docs/TESTING_INTERNALS.md
    • Verify: free ship-template test pins the table, keys, budget stop and unmeasured label
  • T6 (P2, human: ~1h / CC: ~10min + Mac time) — journey measurement and release notes — timed macOS zsh run, CI zsh probe check, issue-close verification commands, "What changes for you"
    • Surfaced by: Codex DX 2, DX M3, M7, M8, M9 / DX-8, DX-9
    • Files: .github/workflows (macOS job), CHANGELOG.md, BROWSER.md
    • Verify: PR body records the timed run (or says unmeasured); macOS CI job green
  • DX-1 (A1 locates itself or says why it cannot). The two helpers share one self-locate block with one resolution order: ${BASH_SOURCE[0]} under bash or ${(%):-%x} under zsh, made absolute with $PWD when relative; else $GSTACK_ROOT/bin when GSTACK_ROOT is exported and holds the helper (the variable the runtime prelude in scripts/resolvers/runtime-root.ts already uses); else the helper fails loudly instead of guessing: gstack: cannot locate gstack-codex-probe (shell: <name>). Source it from bash or zsh, or export GSTACK_ROOT=<install dir>. https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location. The Claude-only default path in bin/gstack-codex-probe and the $(pwd) fallback in bin/gstack-egress-lib.sh (both in commit f0531a2) are removed. The class guard adds an unknown-shell case and a bare-name (source gstack-codex-probe via PATH) case. CEO-15's byte-for-byte lint applies after this rework; the two blocks committed in f0531a2 differ today and must be unified first. Outside CI, a missing zsh prints zsh not installed: install zsh to run the sourced-helper guard (CI requires it) and skips; under CI=true it fails, as A1 already states.
  • DX-2 (one token per state, absolute doc links). The rate-limit state is spelled rate_limited everywhere: probe output CODEX_MODE: unverified (rate_limited), probe state, gate reason and docs anchor codex-rate-limited. Its troubleshooting row says: at probe time the review still runs; a mid-review 429 means that review failed, so coverage is missing. Every new user-facing message prints a full https://github.com/garrytan/gstack/blob/main/docs/<file>.md#<anchor> URL (the pattern autoplan/bin/phase-publication-hook.ts already uses), so the link works from any project; CEO-7's anchor test checks these URLs.
  • DX-3 (quota cache has a way out). The quota_exhausted output prints Codex's own line, when the gstack cache expires, and the retry-now command: GSTACK_CODEX_PROBE_RETRY=1 bypasses the negative cache for one probe (documented in the anchor row, along with deleting <state root>/.codex-model-probe). The probe HINT committed in 597e483 ("usage or rate limit", no anchor) is rewritten to match. A stub test covers recovery: cached quota_exhausted, then GSTACK_CODEX_PROBE_RETRY=1 with a stub that now succeeds, gives MODEL_OK without changing model, config or credentials.
  • DX-4 (A4 recovery keeps the user's work). The too_large denial reads: "This session's journal is MiB, over the 32 MiB limit /autoplan can verify. Run /context-save, start a new session (resume and compact keep writing this journal), run /context-restore, then /autoplan ; or run /plan-ceo-review, /plan-devex-review and /plan-eng-review individually." followed by its full anchor URL. The docs row says the cap has no user override and why (the guard must read the whole parent journal to verify ownership).
  • DX-5 (no dead-end refusals). When a CEO-12 post cannot be sent (file write failed, or an identifier failed its grammar), the skill prints the cause with the actual value, the path of the body file it composed, and the exact safe command to post it by hand (for example gh pr comment <n> --body-file <path>). Branch names are validated with git check-ref-format --branch instead of a narrow regex, so valid refs with +, @ or non-ASCII characters are accepted. After a successful post the block deletes its temp file. B5's refusal names the docker.exe path it found, says a user-writable directory is untrusted for a child that carries registry credentials, gives the fix (install Docker Desktop under Program Files, or run /cso static-only), states it is not overridable, and carries its anchor. CEO-21's setup output lists the hooks it registered and skipped, says "this is a gstack bug; report :", and on an upgrade says "your previous install is still active". B4, when it leaves a real node_modules in place and the Node/Playwright check then fails, prints the command that fixes it and its anchor.
  • DX-6 (upgrades are protected before the checkout changes). bin/gstack-session-update fast-forwards the live checkout before setup runs (git merge --ff-only around line 198), and Claude Code runs hooks straight from that checkout, so CEO-21's staged check alone cannot protect auto-updates. The updater parse-checks every hook shim and TypeScript entry at the incoming revision (from git archive <incoming> into a temp directory) before the fast-forward, using the same check as CEO-21. A failure holds the update, the same way the existing BUN_HELD hold works, and records the reason and file:line in the update status. A test runs the updater against a fixture remote whose incoming revision has one unparseable shim, with existing registrations, and asserts the checkout did not move and the old hooks still run. A manual git pull followed by ./setup cannot be checked before the pull; setup reports it with DX-5's message.
  • DX-7 (measure-loop controls are named and capped). A6's controls are gstack config keys with the CEO-9 defaults: ship_measure_rule_trials (10), ship_measure_behavior_panels (4), ship_measure_judge_outputs (10), ship_measure_ask_per_trial_usd (2), ship_measure_budget_usd (25: a total cap per red case across baseline, repair rounds and panel scoring, enforced by the coordinator before each batch, including parallel workers), ship_measure_max_rounds (3) and ship_rerun_backend (local by default; ubicloud only when set, so Ubicloud spend is opt-in). /ship prints the per-kind table (trials, pass bar, panels, budget) before it runs; the same table is in docs/TESTING_INTERNALS.md and in the ship skill's user-facing text. When the budget is exhausted, /ship stops with the named-red report. A documented skip ("this red is infrastructure, do not measure") labels the case unmeasured in the PR body; it never counts as a pass. A6's "default 10" means the rule-kind default; behavior and judge cases use CEO-9's per-kind counts.
  • DX-8 (the target journey is measured). The PR records one timed run of the persona's journey on a macOS machine with zsh as the login shell: start of /gstack-upgrade (or ./setup) to the first skill preflight that shows CODEX_MODE: ready, and a second run with a stub quota response that shows CODEX_MODE: quota_exhausted with the reset line; it records steps, any restart needed and elapsed time against the 2-5 minute target. The implementer uses Garry's paired Mac when available; without a Mac the PR says the journey is unmeasured. A free CI check on the existing macOS runner sources the probe under zsh and runs _gstack_codex_select_model exec (no paid call).
  • DX-10 (DX amendments govern where they differ from CEO ones). Where a DX amendment and a CEO amendment differ, the DX amendment governs: DX-7's opt-in ship_rerun_backend replaces CEO-1's automatic Ubicloud choice when UBICLOUD_API_KEY is set; DX-5's git check-ref-format --branch replaces CEO-12's example branch regex; DX-2's rate_limited spelling replaces CEO-14's rate limited.
  • DX-9 (users can verify and learn what changed). Each issue-close comment (Tracker hygiene) includes a copy-paste check with its expected output; for #3024: zsh -c 'source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_select_model exec' should print CODEX_MODEL: <model> (exec; source: ...), not invalid. The CHANGELOG entry has a "What changes for you" list: the new messages and their links, quota_exhausted skipping Codex for the rest of the run, setup and the auto-updater refusing unparseable hooks, the router wording (with a one-line command users can run to update their own CLAUDE.md section), the B7 consent rule, and the DX-7 controls. B10's BROWSER.md line states the effect plainly: "set GSTACK_DISABLE_GPU=off to enable WebGL (off is the only recognized value)".

Phase 3: Eng review (FULL_REVIEW, last)

Step 0: Scope Challenge

Target: this plan (docs/designs/FIX_WAVE_2026_10_06.md), checked against the branch at 659b197 (17 commits on top of origin/main 55f8151; the parent implemented most items while the review ran). Design doc: this plan. Prior learnings: none. Brain: unreachable.

A. What already exists, per sub-problem (from code reads):

Item Landed on branch Existing code reused Rework owed (accepted amendments vs landed code)
A1 zsh self-locate f0531a2 BASH_SOURCE sites, gstack-state-root.sh The two blocks differ: probe falls back to $HOME/.claude/skills/gstack/bin (bin/gstack-codex-probe:40) and $PWD for bare names; egress-lib falls back to $(pwd) (bin/gstack-egress-lib.sh:52). DX-1, CEO-15.
A2 quota 597e483 probe contract, cache, GATE_OUTCOMES _QUOTA_SIG still includes `rate limit
A3 safe bodies a853e41 generated-bash lint heredoc mechanism, lint scope (CEO-12, DX-5).
A4 too_large ffde420 HARD_CAUSE, guard doc batch/narrowed paths + seam tests (CEO-2); message recipe (DX-4).
A5 OSV e77c532 overrides, .osv-scanner.toml Landed three ignores: braces (planned), plus sprintf-js and katex (not in the plan), each ignoreUntil = 2026-11-30 with reasons but no tracking-issue reference the file header requires; acceptance scan (CEO-22).
A6 measure loop not started scripts/test-paid-shards.ts --case ID --trials N (line 760/830), eval:pass-rates, test:ubicloud full item.
A7 periodic red not started captured run 37272185151 full item (CEO-4).
B1 shell-init 2328cd3 hook diagnostic CEO-17 negative controls.
B2 chain stdin cdc0fc7 cli.ts CEO-8 Windows curation entry, CEO-17 TTY case.
B3 redaction 4441c8f lib/redact-patterns.ts Landed rule is a "reads as a code name" heuristic (readsAsCodeName) gated on a ,;) terminator or braces, not CEO-20's file/annotation context with prefix/entropy precedence.
B4 junction not started setup Node/Playwright check (setup ~2044) full item.
B5 Docker not started GSTACK_CSO_TRUSTED_GIT pattern (lib/cso/process.ts:22) full item.
B6 router e103e57 omitDisabledSkills Other hosts' generated SKILL.md still carry the old wording (.agents/, .factory/, .kiro/, .opencode/, .slate/ skills/gstack/SKILL.md); CLAUDE.md:532 and test/helpers/autoplan-preconfigured-fixture.ts:7 unchanged (CEO-5, CEO-16); CEO-25 live check.
B7 spawned consent 7bfe46b spawned-directive, auq hook none found (static preamble untouched, test added).
B8 hook parse 659b197 setup registration Gate checks a hand list in $CANONICAL_GSTACK_ROOT after the checkout already moved and exits 1 before any registration; CEO-21 and DX-6 require the staged/pre-fast-forward check and per-hook refusal.
B9 Greptile 23520a9 ship greptile section PR: unknown still dispatches; no test (CEO-19).
B10 browse text 5b06b7b cli.ts, BROWSER.md DX-9 wording check.
B11, B12 not started sync-gbrain Step 3.5, Copilot glossary full items.

Minimum changes: every remaining item is already minimal; the largest new surface is A6 (ship prose plus extending the existing --trials runner flag instead of a new runner).

Complexity check: about 55 changed files and no new classes or services (estimate; generated SKILL.md outputs counted once per template). Over the 8-file threshold, so the B selectors run (auto-decided): no feature cuts are proposed (autoplan override: never reduce, P2); structure question auto-decided Original arrangement (one PR, one commit per item; a smaller arrangement would split the PR, which is UC1's territory and stays with Garry). Scope record: feature answers: none (override); structure: A (Original arrangement, auto-decided P2/P6); accepted scope: CEO-1..28 + DX-1..10 + source plan; pending remedies: UC1, T1-T10.

Search check: no new architectural pattern. The pre-fast-forward hook check (DX-6) reuses the updater's existing BUN_HELD hold [Layer 1]; free-text-through-files is standard practice for gh --body-file [Layer 1].

TODOS cross-reference: closes TODOS "C8 router wording behind a routing panel" (CEO-5); touches "placeholder convention for generated bash" (A3 lint is its first enforced piece); "gate-outcome telemetry" stays open; "Codex follow-ups" stays open.

Distribution check: no new binaries; generated SKILL.md for every host must be regenerated (bun run gen:skill-docs --host all), which the landed B6 commit did not do.

Scope Challenge result: scope accepted as-is (MODE FULL_REVIEW).

Scope Challenge findings:

  • [P1] (confidence: 9/10) .agents/skills/gstack/SKILL.md:243 (and .factory, .kiro, .opencode, .slate) — **When in doubt, invoke the skill.** A false positive (invoking a skill that wasn't still present after e103e57 changed SKILL.md.tmpl; other hosts were not regenerated, so the skill-docs freshness check fails and non-Claude hosts keep the old router.
  • [P1] (confidence: 9/10) setup:3713-3738 (landed 659b197) — _hook_file="$CANONICAL_GSTACK_ROOT/$_hook" ... exit 1: the gate runs on the live checkout after an update already moved it, and refuses every registration; conflicts with CEO-21/DX-6.
  • [P2] (confidence: 8/10) ship/sections/greptile.md.tmpl (landed 23520a9) — `PR: none` → skip the dispatch ... Otherwise dispatch dispatches on PR: unknown (<gh error>), including gh missing or a GitLab remote; CEO-19 requires a skip with the reason.
  • [P2] (confidence: 7/10) lib/redact-patterns.ts readsAsCodeName (landed 4441c8f) — heuristic exemption without CEO-20's file/annotation context or prefix/entropy precedence; letters-only secrets that "read as words" in a ,-terminated YAML flow value would be exempt.
  • [P3] (confidence: 8/10) .osv-scanner.toml (landed e77c532) — new ignores for sprintf-js and katex are outside the plan and name no tracking issue, which the file header requires ("Tracking issues are filed at ship time and named in each reason").

Dual voices (Eng)

  • Voice input: /home/user/.gstack/projects/garrytan-gstack/autoplan-eng-ciD8GY/eng-implementation.md (sha256 190bb276...ceed3e).
  • Claude SUBAGENT (eng — independent review): completed; INPUT: eng 190bb276602480889fe75a39bf1288b07075461f1453a761ab272a0c92ceed3e (matches). 19 findings: E1 rework inventory stale (B8 hand list/late/exit 1; B2 empty input boots a daemon; B4 landed a different design); E2 CEO-21's staged swap does not exist for the Claude checkout; E3 A2's run-scoped skip has no carrier across fresh shells; E4 GSTACK_ROOT is set, not exported; E5 dash dies on later syntax before DX-1's message (probed: Syntax error: redirection unexpected at line 76); E6 quota matching reads the echoed prompt; E7 junction delete hazard; E8 B5 trusts env vars and claims an ACL property; E9 readFileSync(0) can throw; E10 B3 exemption can swallow a JWT; E11 A6 seeded case passes without the fix (slots never reset); E12 local parallel reruns create contention; E13 hand-listed hook sets drift; E14 Dockerfile.ci lacks zsh; E15 relative-path case untested; E16 file-write location prompts or is denied; fixed /tmp/greptile_*.json paths; E17 lint as ratchet; E18 budget has no owner script; E19 DX-6 ordering after BUN_HELD, anchor test should resolve locally.
  • Codex SAYS (eng — architecture challenge) (gpt-6-astra): OUTSIDE_STATUS: completed provider=codex host=claude, VERDICT: findings, FINDINGS: P1, exit 0. Full output:
Reviewed code through `9618336`, including B4, which landed during this review. No skill files were read and no files were changed.

1. **[P1] DX-5 makes branch validation unsafe for shell interpolation. Confidence: high.**
   `git check-ref-format --branch` accepts `fix;id`, ``fix`id` ``, and `fix$(id)`; I verified this without creating refs. Git validity therefore cannot replace CEO-12's shell-safety constraint. The existing generator interpolates identifiers into commands at generate-test-failure-triage.ts:14, while the current lint only checks selected text arguments at generated-bash-lint.ts:35.
   **Fix:** Transport identifiers as data before validating them—prefer argument arrays, or read them from files into quoted variables. Never substitute their bytes into shell source. Include shell metacharacters in the identifier tests, even when Git accepts them.

2. **[P1] B8's proposed check misses broken imported hook code. Confidence: high.**
   Even after moving validation before activation, `bun build --no-bundle` checks only the entry file. Hooks import substantial code: phase-publication-hook.ts:9 imports the snapshot implementation and transcript helpers. An unparseable or missing dependency passes the proposed entry-only gate at setup:3732, then triggers the shim's denial backstop at phase-publication-hook:5. Missing TypeScript entries are also silently skipped. The current fixture replaces entries with trivial modules, hiding this gap (setup-hook-syntax-gate.test.ts:30).
   **Fix:** Validate required files and the complete local import graph without executing hooks. Test missing entries, broken imported modules, and upgrades with existing registrations.

3. **[P1] B3's entropy amendment contradicts its intended exemption. Confidence: high.**
   CEO-20 gives entropy precedence, but the values being exempted already exceed the detector's threshold: `SessionState,` measures 3.085 bits/character and `{turn.requestId` measures 3.374; the threshold is 3.0 at redact-patterns.ts:904. Conversely, the landed word-shape heuristic at redact-patterns.ts:602 suppresses `API_KEY=VelvetRiverOrbitSunset;` and `password: {RiverStoneAmberCedar}`; both returned zero `env.kv` findings in read-only probes.
   **Fix:** Classify source expressions versus literals using actual context, then apply entropy to literals. Carry file context through the scanner where necessary; its current options lack it (redact-engine.ts:59). Add controls that demonstrate both sides simultaneously.

4. **[P1] A6 cannot safely use the existing runner as specified. Confidence: high.**
   `runCaseDiagnosis` preserves the behavior threshold of two while expanding the trial count, so `--trials 12` creates a **2/12** passing panel, not four panels with an 11/12 diagnostic target (paid-report.ts:82). `--case` accepts only E2E IDs, excluding standalone judges (test-paid-shards.ts:825). Repeated invocations also default to shared artifact directories, and evidence collection reads all finalized results there (test-paid-shards.ts:103).
   **Fix:** Make A6 a separate runner feature, consistent with the CEO scope challenge. Define explicit diagnostic results, per-kind panel aggregation, judge selection, and unique baseline/repair/trial directories. Test these executable contracts with free fake runners; prose assertions and one rule-kind paid fixture are insufficient.

5. **[P2] DX-7's "hard" spending cap has no enforceable cost bound. Confidence: high.**
   Historical per-trial cost is an estimate. Reserving ten average-cost trials does not bound ten concurrent trials, especially when history is absent. The runner collects actual costs after execution (test-paid-shards.ts:128), and the diagnosis result discards those cost fields when constructing its returned verdict (paid-report.ts:108).
   **Fix:** Define an adapter contract for bounded trial spend, reservation of all concurrent work—including judges—and reconciliation of actual costs. Where the backend cannot enforce a bound, describe the control as an estimated admission budget rather than a guaranteed $25 cap.

6. **[P2] CEO-24 assumes a Windows installation step that does not exist. Confidence: high.**
   The "Windows Setup E2E" workflow runs `bun run build` and checks checkout binaries; it never runs `./setup` (windows-setup-e2e.yml:72). Adding a journey "after setup" therefore does not establish an installed runtime root. Moreover, newly landed B4 uses a source-bundle fallback, not the plan's junction design (cli.ts:125).
   **Fix:** Reconcile B4's chosen architecture first. Add an actual isolated host installation, then invoke its installed binary from an unrelated directory and assert both commands' output. Exercise stale installations and failed dependency resolution.

7. **[P2] B5 fixes executable discovery while leaving deterministic Windows blockers downstream. Confidence: high.**
   Finding `docker.exe` reaches code that rejects native drive-letter configuration paths (docker.ts:55), accepts only `unix:///` endpoints, and requires a filesystem Unix socket (docker.ts:104). A binding test cannot demonstrate usable native Windows Docker operation.
   **Fix:** Explicitly scope B5 to discovery and preserve a clear static-only unsupported-transport outcome, or separately design and validate native endpoint support. Do not close the operational defect based solely on executable binding.

8. **[P2] DX-1's loud self-location failure will be swallowed by its caller. Confidence: high.**
   The generated preflight sources the helper with `2>/dev/null || true`, then suppresses the auth probe's errors and reports `not_authed` (constants.ts:138). Improving the helper's error message alone still leaves users being told to authenticate when installation discovery failed.
   **Fix:** Handle source failure explicitly, retain its diagnostic, and stop before authentication probing. Test the complete generated preflight's output and branching, not only direct helper sourcing.

9. **[P2] B2's empty-input handling still depends on successfully starting a browser. Confidence: high.**
   Empty input becomes an empty command argument at cli.ts:2041, but the CLI calls `ensureServer()` before dispatching it (cli.ts:2062). On a broken Windows install, an empty pipe or argumentless terminal invocation reports a startup failure instead of the promised usage error; it may also launch an unnecessary daemon.
   **Fix:** Reject empty input locally before server startup. Test both terminal and empty-pipe cases with no running daemon and unavailable browser dependencies.

Recommendation: Revise the plan and separate A6 from urgent repairs because shell interpolation remains unsafe, hook validation misses executable dependencies, and the measurement workflow requires substantive runner changes.

(Markdown links in Codex's output were flattened to path:line text.)

ENG DUAL VOICES — CONSENSUS TABLE:
  Dimension                           Claude                Codex                  Consensus
  1. Architecture sound?               yes, after E1/E2      revise (A6 runner,     DISAGREE on verdict, agree on fixes -> ENG-1/2/3/12 (taste T13 on B4 design)
                                                             hook import graph)
  2. Test coverage sufficient?         no (E11, E13-E15)     no (2, 4, 6)           CONFIRMED gap (ENG-3, ENG-5, ENG-12, ENG-13)
  3. Performance risks addressed?      no (E12 contention)   not raised             N/A (single voice) -> ENG-12 parallelism cap
  4. Security threats covered?         no (E6, E8, E10, E16) no (1, 3)              CONFIRMED gap (ENG-6, ENG-8, ENG-9, ENG-11, ENG-14)
  5. Error paths handled?              no (E5, E9)           no (8, 9)              CONFIRMED gap (ENG-5, ENG-10)
  6. Deployment risk manageable?       no (E2, E13, E14)     no (2, 6)              CONFIRMED concern (ENG-2, ENG-3, ENG-7, ENG-9)
CONFIRMED = native + outside agree; primary cannot replace outside. DISAGREE -> taste.

Cross-model notes: both voices independently found the B8 gate gaps (Claude E1/E13, Codex 2), the B3 heuristic failures (Claude E10, Codex 3, with Codex's probes showing two literal secrets exempted), the B2 daemon boot on empty input (Claude E1/E9, Codex 9), the B4 design drift (Claude E1/E7, Codex 6) and the A6 runner gap (Claude E18, Codex 4/5). Codex again recommends splitting A6 out (UC1, now raised in all three phases). Codex 1 overturns DX-5's "check-ref-format replaces the grammar" (accepted as ENG-8). Claude E17's ratchet suggestion is declined: free-text placeholders get no baseline (taste T12).

Section 1: Architecture

  generated SKILL.md (all hosts; regenerate --host all)          ./setup                     bin/gstack-session-update
        |                                                           |                               |
        |  preflight (constants.ts) --source--> gstack-codex-probe  |  hook gate (derived list,     |  fetch -> BUN_HELD check
        |        |  helper_unavailable (ENG-5)       |              |  local-import bundle check)   |  -> HOOK check on
        |        v                                    v              |  -> register passing hooks    |     git archive <incoming>
        |  CODEX_MODE ready|quota_exhausted|rate_limited|...        |  (ENG-2/3)                    |  -> ff-merge or HOLD (DX-6)
        |        |  (errors read from Codex's ERROR lines, ENG-6)   v                               v
        |        v                                     Claude checkout = live hooks      copy-based runtime roots
        |  codex exec --> outside-review-result.ts --> GATE_OUTCOMES (+quota_exhausted, +rate_limited) --> docs anchors
        |
        |  posting blocks: mktemp path -> agent file-write -> gh --body-file (CEO-12, ENG-8, ENG-14)
        |
        '--> /ship tests step --> scripts/ship-measure.ts (NEW, ENG-12) --> scripts/test-paid-shards.ts --case/--trials
                                     | per-kind panels, unique dirs, admission budget, table      | (or project eval cmd)
                                     '--> free reruns: shard file list, retry off, parallelism cap (CEO-1, ENG-12)

  autoplan hook --> readOwnedClaudePublicTranscript (32 MiB owned + batch caps, too_large) (A4, CEO-2)
  browse cli --> chain: stdin/TTY/empty handled before ensureServer (ENG-10); Windows runtime root -> .source-path bundle,
                 version-skew guard (ENG-9)
  lib/redact-engine.ts --(file path)--> redact-patterns env.kv: source-context vs literal (ENG-11)
  lib/cso: docker.exe discovery (known-folder roots, realpath) -> unsupported-transport outcome (ENG-9)

Findings:

  • [P1] (confidence: 9/10) setup hook gate (landed) checks live files and exits before registering anything — ENG-2/ENG-3.
  • [P1] (confidence: 9/10) scripts/lib/paid-report.ts runCaseDiagnosis keeps the behavior threshold at 2 while expanding trials (Codex probe) — A6 needs its own runner, ENG-12.
  • [P2] (confidence: 8/10) B4 landed .source-path design differs from the plan's junction — adopt landed design with a version-skew guard, ENG-9 (taste T13).
  • [P2] (confidence: 8/10) lib/cso/docker.ts accepts only unix:/// endpoints — B5 scoped to discovery, ENG-9. Dispositions: all accepted via auto-decision (audit rows 59-63).

Section 2: Code quality

  • [P1] (confidence: 9/10) Other hosts' generated SKILL.md are stale after e103e57 (ENG-1).
  • [P1] (confidence: 9/10) lib/redact-patterns.ts readsAsCodeName exempts API_KEY=VelvetRiverOrbitSunset; (Codex probe) — ENG-11.
  • [P2] (confidence: 8/10) bin/gstack-codex-probe:37-41 and bin/gstack-egress-lib.sh:52-55 duplicate the self-locate block with different fallbacks — one shared block (DX-1, ENG-5). Shared-code rubric: two first-party callers with identical intent; the block is about 12 lines, duplicated by necessity (each file is sourced standalone), so it stays duplicated byte for byte under a lint rather than extracted into a third sourced file (which would itself need locating).
  • [P2] (confidence: 8/10) review/greptile-triage.md fixed /tmp/greptile_*.json paths — ENG-14.
  • [P3] (confidence: 7/10) hand-listed hook sets in setup and (planned) in the updater — derived list, ENG-3. Stale diagrams: bin/gstack-codex-probe's contract comment (lines ~164-180) must list quota_exhausted and rate_limited; no other touched ASCII diagram is invalidated.

Section 3: Test review

Framework: bun test (CLAUDE.md, package.json), free suite via bun run test, paid via scripts/test-paid-shards.ts.

CODE PATHS                                                      USER FLOWS
[+] bin/gstack-codex-probe (A1/A2)                              [+] Outside voice after upgrade (macOS zsh)
  ├── self-locate: bash [★★ TESTED regression-issue3024]           ├── [GAP] [→E2E-lite] timed journey (DX-8, manual + CI zsh check)
  │   zsh [★★ TESTED]; dash [GAP] ENG-5; relative+cd [GAP] ENG-5   └── [GAP] full generated preflight, helper missing (ENG-5)
  │   bare-name [GAP] DX-1; GSTACK_ROOT fallback [GAP] DX-1
  ├── model probe: quota [★★ TESTED codex-model-probe, needs split] [+] Long session /autoplan
  │   rate_limited [GAP] CEO-14; precedence [GAP] ENG-6              ├── [★★ TESTED] too_large owned path (autoplan-publication-guard)
  │   echoed-prompt negative [GAP] ENG-6; retry-now [GAP] DX-3       └── [GAP] batch + narrowed paths (CEO-2, seam)
  │   fresh-shell skip [GAP] ENG-4                                [+] Greptile reply / test-failure issue
  └── classifyOutsideReview stderr [★★ TESTED, needs anchoring]      ├── [GAP] hostile body via real generated text (CEO-12)
[+] setup hook gate (B8)                                             ├── [GAP] metachar identifier refused (ENG-8)
  ├── [★★ TESTED setup-hook-syntax-gate] broken shim                 └── [GAP] unsent -> manual command printed (DX-5)
  ├── [GAP] broken local import, missing entry (ENG-3)            [+] /ship red paid case
  ├── [GAP] derived list == registrations (ENG-3)                    ├── [GAP] [→EVAL] seeded case, behavior panel (CEO-3, ENG-13)
  └── [GAP] partial registration + exit != 0 (ENG-2)                 ├── [GAP] negative control unrelated "fix" (ENG-13)
[+] bin/gstack-session-update (DX-6)                                 └── [GAP] ship-measure fake runners per kind, budget stop (ENG-12)
  └── [GAP] incoming bad hook -> HOLD, checkout unmoved           [+] Free flake rerun
[+] browse chain (B2)                                                └── [GAP] retry off, isolated state, parallelism cap (CEO-1, ENG-12)
  ├── [★★ TESTED cli-chain-stdin] piped flow                      [+] Router (B6)
  └── [GAP] TTY / empty / EAGAIN before ensureServer (ENG-10)        ├── [★★ TESTED gen-skill-docs] new wording, Claude host
[+] redact env.kv (B3)                                               ├── [GAP] all hosts regenerated (freshness) (ENG-1)
  ├── [★ TESTED redact-engine, positive TS case only]                └── [→EVAL] routing panel (CEO-5)
  └── [GAP] literal controls + JWT + file context (ENG-11)        [+] Windows (B2/B4/B5/B12)
[+] Windows runtime root (B4)                                        ├── [★★ TESTED config.test] source bundle resolution
  └── [GAP] version-skew refusal; isolated install journey (ENG-9)   └── [GAP] curated subset entries (CEO-8); unsupported transport (ENG-9)
[+] CSO docker discovery (B5) [GAP] known-folder roots, reparse, outcome (ENG-9)
[+] spawned consent (B7) [★★ TESTED spawned-consent-rule]
[+] OSV (A5) [★★ TESTED osv-config-wiring, deps-smoke]; acceptance scan [GAP] CEO-22
[+] anchors (CEO-7) [★★ TESTED troubleshooting-anchors, gate rows only]; URL + local resolution [GAP] ENG-15

COVERAGE: 11/41 paths tested (27%)  |  Code paths: 9/29  |  User flows: 2/12
QUALITY: ★★★:0 ★★:10 ★:1  |  GAPS: 30 (1 E2E-lite manual, 2 eval)

Every gap above maps to an accepted amendment (CEO-, DX-, ENG-*), so each is a required test, not a pending choice. LLM/eval scope (CLAUDE.md "Prompt/LLM changes" patterns: skill templates, resolvers, preamble): the PR gate's outside-voice cases (A2 preflight), the routing panel (B6), the new A6 seeded behavior case (periodic), A7's 10-trial measurement (diagnostic), and the full PR gate once. Tests made obsolete: the landed test/codex-model-probe.test.ts assertions that map 429 to quota (lines ~59, ~464) are rewritten, not deleted; the landed test/setup-hook-syntax-gate.test.ts fixture that replaces entries with trivial modules is extended with real imports. Test plan artifact: /home/user/.gstack/projects/garrytan-gstack/user-garrytan-fix-wave-oct6-eng-review-test-plan-20261006-184711.md (written, 46 lines).

Section 4: Performance

  • [P2] (confidence: 8/10) CEO-1 local reruns: 10 parallel processes on the 4-core Capy machine (nproc = 4) at the shard's own concurrency would mostly measure contention — ENG-12 caps parallelism.
  • The hook's journal read stays capped (32 MiB) and runs only on phase-entry calls. The probe's quota cache saves one Codex round trip per skill while exhausted. DX-6 adds one git archive plus a few bun build runs per update check (seconds, only when an update is incoming). No unbounded loops or caches added.

Failure modes (Eng)

  NEW PATH                       | REALISTIC FAILURE                               | TEST?  | ERROR HANDLING     | USER SEES
  -------------------------------|-------------------------------------------------|--------|--------------------|---------------------------
  self-locate under dash         | syntax error before message (today)             | ENG-5  | return 1 early     | clear message + anchor
  preflight source failure       | swallowed, reported as not_authed (today)       | ENG-5  | helper_unavailable | clear message
  quota match on echoed prompt   | false quota_exhausted, Codex skipped 15 min     | ENG-6  | anchored match     | correct reason
  hook import broken on upgrade  | passes entry check, hook denies every call      | ENG-3  | bundle check       | refusal with file:line
  auto-update with bad hook      | checkout moved, hooks blocked                   | DX-6   | HOLD               | update held + reason
  A6 measurement                 | 2/12 panel threshold, shared artifact dirs      | ENG-12 | ship-measure.ts    | correct per-kind table
  A6 seeded case                 | passes without the fix                          | ENG-13 | slot reset + neg.  | case fails correctly
  B3 literal secret              | exempted by word-shape heuristic (today)        | ENG-11 | context rule       | secret redacted
  B2 empty input                 | daemon boot / startup failure                   | ENG-10 | local usage error  | usage + exit 1
  B4 version skew                | runtime CLI vs checkout server mismatch         | ENG-9  | refusal            | clear message
  B5 Windows Docker              | discovery works, transport unsupported          | ENG-9  | explicit outcome   | static-only, anchor

Critical gaps (no test AND no handling AND silent): 0 after the accepted amendments. Before them, 3 were silent on the branch: the B3 literal exemption, the swallowed helper failure, and the echoed-prompt quota match.

NOT in scope (Eng)

  • Argument-array posting helper (CEO-12's stronger form): deferred (taste T8).
  • Native Windows Docker named-pipe transport for /cso: deferred to TODOS.md (ENG-9); B5 ships discovery plus an honest outcome.
  • Lint ratchet with a shrinking baseline (Claude E17): declined; free-text placeholders get no baseline (taste T12).
  • Bounded owned-journal read: deferred P1 (CEO-2).
  • Standalone readiness command: deferred (DX).

What already exists (Eng)

Reused: scripts/test-paid-shards.ts --case/--trials and scripts/lib/paid-report.ts (wrapped by the new scripts/ship-measure.ts, not duplicated), eval:pass-rates cost history, scripts/ubicloud/test-free.sh, setup's _hook_command_path registration list, the updater's BUN_HELD hold, GATE_OUTCOMES, test/troubleshooting-anchors.test.ts, test/setup-hook-syntax-gate.test.ts, browse/test/cli-chain-stdin.test.ts, browse/test/config.test.ts, test/spawned-consent-rule.test.ts, test/osv-config-wiring.test.ts, scripts/lib/windows-curation.ts. New: scripts/ship-measure.ts (justified: the existing diagnosis path cannot aggregate per kind or isolate artifacts, Codex 4).

Worktree parallelization strategy

Step Modules touched Depends on
S1 probe + preflight rework (A1/A2: DX-1..3, ENG-4..7) bin/, scripts/resolvers/, lib/, docs/, test/ —
S2 posting safety (A3: CEO-12, ENG-8, ENG-14) review/, scripts/resolvers/preamble/, ship/sections/, spec/, test/helpers/ —
S3 hook gate + updater (B8: ENG-2/3, DX-6) setup, bin/gstack-session-update, test/ —
S4 transcript + guard (A4: CEO-2, DX-4) lib/, autoplan/bin/, docs/ —
S5 browse + Windows (B2/B4/B5/B12: ENG-9/10, CEO-8/24) browse/, lib/cso/, .github/workflows/, scripts/lib/ —
S6 redaction (B3: ENG-11) lib/redact-*.ts, test/ —
S7 router regen + Tier 2 text (B6/B9/B10/B11) SKILL.md.tmpl, CLAUDE.md, ship/, sync-gbrain/, all generated hosts S1, S2 (shared generated outputs)
S8 A6 runner + ship prose (ENG-12/13, DX-7) scripts/, ship/sections/, docs/TESTING_INTERNALS.md, test/ S2 (ship/sections)
S9 A7 replay + measurement test/fixtures, paid runs S8

Parallel lanes: Lane A: S1 → S7; Lane B: S2 → S8 → S9; Lane C: S3; Lane D: S4; Lane E: S5; Lane F: S6. Execution order: launch A-F; merge B's S2 and A's S1 before S7; S9 last. Conflict flags: generated SKILL.md files (S1, S2, S7) must be regenerated once at the end from merged templates; ship/sections/tests.md.tmpl is shared by S2 and S8.

Eng Implementation Tasks

  • T1 (P1, human: ~2h / CC: ~10min) — router — regenerate every host and update CLAUDE.md + fixture
    • Surfaced by: Scope Challenge — .agents/skills/gstack/SKILL.md:243 still says "When in doubt"
    • Files: SKILL.md.tmpl outputs for all hosts, CLAUDE.md, test/helpers/autoplan-preconfigured-fixture.ts
    • Verify: bun run gen:skill-docs --host all && git diff --exit-code (freshness) && bun test test/gen-skill-docs.test.ts
  • T2 (P1, human: ~1d / CC: ~40min) — setup + updater — derived hook list, local-import bundle check, partial registration, pre-fast-forward hold
    • Surfaced by: Section 1 — setup hook gate (ENG-2, ENG-3, DX-6)
    • Files: setup, bin/gstack-session-update, test/setup-hook-syntax-gate.test.ts
    • Verify: bun test test/setup-hook-syntax-gate.test.ts (+ updater hold test)
  • T3 (P1, human: ~1d / CC: ~45min) — /ship — scripts/ship-measure.ts with per-kind panels, unique dirs, admission budget, parallelism cap; seeded case fixes
    • Surfaced by: Codex 4/5, Claude E11/E12/E18 (ENG-12, ENG-13)
    • Files: scripts/ship-measure.ts, scripts/lib/paid-report.ts, ship/sections/tests.md.tmpl, test fixtures
    • Verify: free fake-runner tests for each kind and the budget stop; seeded case negative control
  • T4 (P1, human: ~4h / CC: ~25min) — redaction — context-based classification with file path through the engine; literal controls
    • Surfaced by: Codex 3, Claude E10 (ENG-11)
    • Files: lib/redact-patterns.ts, lib/redact-engine.ts, test/redact-engine.test.ts
    • Verify: bun test test/redact-engine.test.ts
  • T5 (P1, human: ~3h / CC: ~20min) — probe + preflight — dash early return, helper_unavailable mode, anchored quota/rate matching, fresh-shell skip test
    • Surfaced by: Claude E3/E5/E6, Codex 8 (ENG-4, ENG-5, ENG-6)
    • Files: bin/gstack-codex-probe, bin/gstack-egress-lib.sh, scripts/resolvers/constants.ts, lib/outside-review-result.ts, tests
    • Verify: bun test test/codex-model-probe.test.ts test/outside-review-result.test.ts test/regression-issue3024-zsh-sourced-helpers.test.ts
  • T6 (P1, human: ~3h / CC: ~20min) — posting — metachar-safe identifiers, data transport for remote identifiers, verified write location, mktemp for greptile JSON
    • Surfaced by: Codex 1, Claude E16 (ENG-8, ENG-14)
    • Files: review/greptile-triage.md, scripts/resolvers/preamble/generate-test-failure-triage.ts, test/helpers/generated-bash-lint.ts
    • Verify: hostile-payload tests through generated text
  • T7 (P2, human: ~3h / CC: ~20min) — browse/Windows — B2 local usage error, B4 version-skew guard, isolated install journey, B5 known-folder roots + unsupported-transport outcome
    • Surfaced by: Claude E7-E9, Codex 6/7/9 (ENG-9, ENG-10)
    • Files: browse/src/cli.ts, lib/cso/process.ts, lib/cso/docker.ts, .github/workflows/windows-setup-e2e.yml, scripts/lib/windows-curation.ts
    • Verify: browse/test/cli-chain-stdin.test.ts, browse/test/config.test.ts, Windows lanes
  • T8 (P2, human: ~1h / CC: ~10min) — CI + hygiene — zsh in Dockerfile.ci, anchor test local resolution, OSV tracking issues
    • Surfaced by: Claude E14/E19, Scope Challenge P3 (ENG-1, ENG-7, ENG-15)
    • Files: .github/docker/Dockerfile.ci, test/troubleshooting-anchors.test.ts, .osv-scanner.toml
    • Verify: bun test test/troubleshooting-anchors.test.ts test/osv-config-wiring.test.ts

TODOS.md updates (collected from every phase; not persisted)

The parent's instruction for this review forbids editing repository files other than the plan, so these TODOS.md entries are recorded here for the implementation PR to write (CEO-6 already requires it), in the repo's TODOS format:

  1. B9 full: Greptile in parallel during /ship (#3020) — What: push early so Greptile reviews while /ship runs its own passes, then merge findings. Why: removes the "no PR yet" gap. Effort L/M. P2. Depends on: B9 skip landed.
  2. Bounded or incremental owned-journal read (P1, next wave) — acceptance: /autoplan completes with a >100 MiB journal; phase-entry latency and peak memory measured on macOS and Linux. Depends on: CEO-2 seams.
  3. Executed-subcommand Codex probe (CEO-28) — removes the caller-shell dependency. Effort M. P2.
  4. Argument-array posting helper (CEO-12 stronger form) — Effort M. P2.
  5. Native Windows Docker transport for /cso (ENG-9) — named-pipe endpoint support in lib/cso/docker.ts. Effort M. P2.
  6. Standalone readiness command (DX: gstack doctor or a Codex row in ./setup --status) — Effort S/M. P3.
  7. Per-reason telemetry — already in TODOS (gate-outcome telemetry, P3); no new entry.
  8. Conditional: B11 if the first live probe fails (CEO-27); B5 side note if it does not reproduce (CEO-10); A7 P1 if it stops red (CEO-4); any Tier 2 item reverted (CEO-11).

Completion summary (Eng)

  • Step 0: Scope Challenge — scope accepted as-is (5 findings: 2 P1, 2 P2, 1 P3)
  • Architecture Review: 4 issues found
  • Code Quality Review: 5 issues found
  • Test Review: diagram produced, 30 gaps identified (all mapped to accepted amendments)
  • Performance Review: 1 issue found
  • NOT in scope: written
  • What already exists: written
  • TODOS.md updates: 6 items proposed (+2 conditional groups), not persisted per the parent's instruction; CEO-6 makes the implementation PR write them
  • Failure modes: 0 critical gaps flagged (3 silent gaps on the branch closed by ENG-5, ENG-6, ENG-11)
  • Unresolved decisions: 0 in this review (UC1 and taste T1-T13 held for the gate)
  • Outside voice: codex (gpt-6-astra) completed; native subagent completed
  • Parallelization: 6 lanes, 6 parallel / 3 sequential steps
  • Lake Score: N/A (no coverage-scored questions answered)
  • ENG-1 (rework inventory covers every landed commit). CEO-18's list is extended to every commit on the branch; each item ships only when its row says "matches" or its rework is done. B1 2328cd3: add CEO-17's negative controls. B2 cdc0fc7: see ENG-10. B3 4441c8f: see ENG-11. B6 e103e57: regenerate every host (bun run gen:skill-docs --host all; .agents/, .factory/, .kiro/, .opencode/, .slate/ still carry the old wording), update CLAUDE.md:532 and test/helpers/autoplan-preconfigured-fixture.ts:7 (CEO-5, CEO-16), run CEO-25's live check. B7 7bfe46b: matches. B8 659b197: see ENG-2 and ENG-3. B9 23520a9: PR: unknown must skip with the reason instead of dispatching, plus CEO-19's test. B10 5b06b7b: DX-9 wording check. B4 9618336 and B12 3df850f: see ENG-9 for B4; B12 matches (CEO-8's Windows curation entry still owed). A5 e77c532: the plan now records the two extra ignores it added (sprintf-js via roarr, katex in diagram-render) and their ignoreUntil = 2026-11-30 (consistent with the file's other entries, replacing CEO-17's 2026-12-31); each of the three new ignores names a tracking issue filed at ship time, as the file header requires.
  • ENG-2 (honest scope for the setup hook gate). On the Claude install, CANONICAL_GSTACK_ROOT (~/.claude/skills/gstack) is the git checkout the hooks run from, so by the time ./setup runs the new files are already live; there is no staged swap there. CEO-21's "abort the swap, previous install stays active" applies only to copy-based runtime roots. On the Claude checkout the setup gate refuses to register the failing hooks, registers the rest, reports file and line, and exits non-zero; it never prints DX-5's "your previous install is still active". DX-6 (the updater's pre-fast-forward check) is the protection for auto-updates. The landed gate's early exit 1 before any registration is replaced accordingly.
  • ENG-3 (the hook check covers what the hook runs). The hook list is derived from the registration code (every _hook_command_path argument plus the phase-publication hook), not a hand list; a free test asserts the two stay equal. Each TypeScript entry is checked with a local-import bundle into a temp directory (bun build --target=bun with node_modules packages external), so a broken or missing local import fails the check; a missing entry file fails instead of being skipped. Tests: broken imported module, missing entry, and an upgrade with existing registrations (both for setup and for DX-6's updater check, which runs after the existing BUN_HELD check).
  • ENG-4 (A2's run-scoped skip has a carrier). Skill blocks run in fresh shells, so "skip the remaining Codex calls in that run" is carried by the 15-minute negative cache; a test runs two consecutive fresh-shell preflight blocks against a quota stub and asserts the second makes no Codex call.
  • ENG-5 (self-locate works where it matters and fails loudly elsewhere). DX-1's fallback accepts a set (not necessarily exported) GSTACK_ROOT that holds the helper. The shared block ends with return 1 2>/dev/null || exit 1 after printing its message and before any bash/zsh-only syntax, so dash and sh reach the message instead of a syntax error. The generated preflight (scripts/resolvers/constants.ts, which today runs source ... 2>/dev/null || true) keeps the source step's diagnostic and reports a distinct CODEX_MODE: helper_unavailable with that line and its anchor, instead of falling through to not_authed. Class-guard cases: dash (message, no syntax error), relative-path source followed by cd, bare-name source via PATH, and the full generated preflight under each.
  • ENG-6 (quota and rate-limit matching reads Codex's error, not the echoed prompt). Both the probe's signatures and classifyOutsideReview's stderr matching read only Codex's own error lines (the trailing ERROR: / stream error lines), not the whole stderr, which echoes the session prompt. Tests: an echoed prompt that contains "usage limit" and insufficient_quota with a non-quota exit stays execution_failed; a 429 body that also carries insufficient_quota resolves to quota_exhausted (quota takes precedence over rate limit).
  • ENG-7 (Dockerfile.ci installs zsh). .github/docker/Dockerfile.ci adds zsh, as A1 already states, so the A1 guard runs (and fails loudly when missing) inside the eval container too.
  • ENG-8 (identifiers are shell-safe, not only git-valid). git check-ref-format --branch accepts fix;id and fix`id`, so DX-5's branch check also rejects shell metacharacters ($, backtick, ;, |, &, (, ), <, >, quotes, \, whitespace) and refuses with DX-5's message; numeric ids must match ^[0-9]+$. Identifiers that come from remote data (PR head branch names, file paths from a diff or PR) travel as data, read from the agent-written file into quoted variables, never substituted into shell source. Tests include metacharacter identifiers that git accepts.
  • ENG-9 (B4 uses the landed design, and B5 is honest about Windows Docker). B4 keeps the landed runtime design (9618336: the Windows runtime root starts the server from the source checkout's bundle through .source-path) instead of the plan's mklink /J junction, which avoids a runtime-root delete following the junction into the checkout. It adds a version-skew guard: when the runtime root's CLI version and the checkout's server version differ, the CLI refuses with an anchor and the ./setup fix. CEO-17's junction mechanics and CEO-8's junction test are replaced by a test that the runtime root resolves the checkout bundle and refuses on version skew; CEO-24's journey step becomes a real isolated install (the current windows-setup-e2e.yml runs bun run build but never ./setup), then the installed browse from an unrelated directory piping a two-command chain flow. B5 resolves the allowlisted roots through the known-folder API (PowerShell [Environment]::GetFolderPath), not the process environment, resolves the candidate's real path before the prefix check, and rejects reparse points; its refusal says "outside the trusted install locations". Because lib/cso/docker.ts accepts only unix:/// endpoints, B5 is scoped to discovery: with docker.exe bound, /cso reports a clear "native Windows Docker transport is not supported yet; static assessment only" outcome with an anchor, and native named-pipe transport goes to TODOS.md. #3028 is closed with that explanation, not as fixed operation.
  • ENG-10 (B2 never boots a daemon for empty input). Empty piped input, a TTY with no arguments, and a readFileSync(0) error (EAGAIN, EOF) all produce the usage error and exit 1 before ensureServer(). Tests run with no daemon and with browser dependencies unavailable.
  • ENG-11 (B3 classifies by context; replaces CEO-20's precedence rule). Identifiers like SessionState, exceed the 3.0 bits/char entropy threshold, so "entropy takes precedence" cannot hold. B3 instead passes the file path into the scanner (lib/redact-engine.ts options) and classifies a value as a source expression only in a TypeScript/JSX file after a : type annotation or inside a JSX {...} expression; entropy and known-prefix rules apply to everything classified as a literal. The landed word-shape heuristic (readsAsCodeName) is replaced. Controls that must flag: API_KEY=VelvetRiverOrbitSunset; in a .env/YAML file, password: {RiverStoneAmberCedar} in YAML, a JWT in a .ts file, a quoted literal, a brace-wrapped literal, a literal concatenated to a property read. Controls that must not flag: session: SessionState, and key={turn.requestId + turn.role} in .ts/.tsx.
  • ENG-12 (A6 runner owns the measurement; budget is an admission budget). A6 ships as a runner feature, scripts/ship-measure.ts, which /ship calls and which owns: per-kind aggregation (rule: N trials; behavior: panels of 3, each at its own 2-of-3 threshold, not one 2-of-12 panel as runCaseDiagnosis would produce today; judge: outputs scored by 3-sample panels, with standalone judge ids accepted), unique artifact directories per baseline, repair round and trial, the diagnostic label, and the per-case table. DX-7's ship_measure_budget_usd is an estimated admission budget: before admitting a batch it reserves the estimated cost of every concurrent trial (judges included), reconciles actual costs after each batch, and stops when reserved plus spent would exceed the budget; the PR table shows estimated and actual spend. Local reruns (CEO-1) cap parallelism at max(1, floor(cores / shard concurrency)), queue the rest and record the parallelism used. Free tests drive scripts/ship-measure.ts with fake runners for every kind, the budget stop and the unmeasured skip.
  • ENG-13 (A6 seeded case cannot pass without the fix). The stub fails a trial only while the list is unsorted and its slot number mod 10 is 2, 5 or 9; slots reset per measurement batch. The case asserts the closing measurement ran at least 10 trials against the sorted file, and a negative control (a "fix" that edits an unrelated line) re-measures at 7 of 10 and never reaches the gate command.
  • ENG-14 (A3 file location and fixed temp paths). The implementation verifies, against the Claude Code version pinned in .github/docker/Dockerfile.ci, where the host's write tool can create the body file without a permission prompt in default mode and in headless or spawned sessions (candidates: a project-local .gstack/tmp/ excluded through .git/info/exclude, or $GSTACK_STATE_ROOT/tmp), uses the first that works with mode 0600, and records the result in the PR. review/greptile-triage.md's fixed /tmp/greptile_line.json and /tmp/greptile_top.json move to mktemp paths.
  • ENG-15 (anchor test resolves locally). CEO-7's anchor test accepts both relative anchors and DX-2's https://github.com/garrytan/gstack/blob/main/docs/...#anchor URLs and resolves both against the docs in the working tree, so a new anchor does not fail CI before it reaches main.

Phase 4: Pre-Gate Verification

Phase Required outputs Status
CEO premise challenges (10); Sections 1-10 with findings or examined/no-findings (11 skipped, no UI); Error & Rescue map; Failure Modes registry; NOT in scope; What already exists; dream state delta; Completion Summary; consensus table present
Design skipped (no UI scope); not a completed review skipped
DX 8 dimension scores; journey map; empathy narrative; TTHW (never -> 2-5 min target); DX Implementation Checklist; consensus table present
Eng scope challenge from code reads; architecture diagram; codepath/user-flow test diagram; test plan on disk (/home/user/.gstack/projects/garrytan-gstack/user-garrytan-fix-wave-oct6-eng-review-test-plan-20261006-184711.md); NOT in scope; What already exists; failure modes with critical gaps (0); Completion summary; consensus table present

Voices: every phase's native subagent and Codex (gpt-6-astra) voice completed with matching INPUT hashes; design skipped. Decision Audit Trail: 77 rows. Known incomplete items: the CEO spec-review loop ended at its 3-launch cap with score 6 (FAIL) and one open clarity issue (amendments override item text rather than being folded in); no ## GSTACK REVIEW REPORT, review logs or TODOS.md writes were made (autoplan skip-lists the per-skill report, and the parent reserved logs and repository writes); the tasks aggregator dropped the CEO task list because the branch gained more than five commits during the review (its 5-commit window), so CEO tasks are carried from the plan's markdown section.

Cross-phase themes (raised independently in 2+ phases): A6 coupled to the urgent repairs (CEO, DX, Eng) -> UC1; untrusted text and identifiers reaching the shell (CEO, DX, Eng) -> CEO-12, DX-5, ENG-8; hook safety across upgrades (CEO, DX, Eng) -> CEO-21, DX-6, ENG-2/3; the long-session journal cliff (CEO, DX) -> CEO-2, DX-4; Windows claims vs evidence (CEO, DX, Eng) -> CEO-24, ENG-9; measurement honesty (CEO, Eng) -> CEO-23, ENG-12/13; plan drift against a branch being implemented during review (CEO, spec review, Eng) -> CEO-18, ENG-1.

Decision Audit Trail

# Phase Decision Classification Principle Rationale Rejected
1 CEO Mode SELECTIVE EXPANSION Mechanical override autoplan fixes the mode SCOPE REDUCTION (file-count rule)
2 CEO Keep the whole wave in one PR (HOLD check 1) Mechanical P6 user's standing one-PR preference split per item
3 CEO Keep B4/B5/B11 in scope (HOLD check 2) Mechanical P1 never reduce a complete plan defer them
4 CEO Correct B6 text (C8 half already shipped) Mechanical P5 factual correction from code read leave stale text
5 CEO Correct #3033 overlap list and base Mechanical P5 pr3033-files.txt, git log leave stale text
6 CEO X1 raise A4 cap by measurement Taste -> reversed P2, then P5 both voices: a bigger cap moves the cliff; now cap stays 32 MiB + P1 follow-up measured raise
7 CEO X2 free-suite flake measurement in A6 Mechanical P2 same file, part of #3034 omit
8 CEO X3 cache quota until reset time Mechanical P5 fragile parsing accept
9 CEO X4 per-reason telemetry Mechanical P4 duplicate TODO accept
10 CEO X5 Greptile in parallel deferred Mechanical P3 outside blast radius include
11 CEO X6 seeded stub paid case, periodic tier Taste (T4) P1 keeps paid case near 10 minutes real eval runner inside the case
12 CEO X7 replay A7's captured run first Mechanical P1 AGENTS.md: free replay before paid paid trials first
13 CEO CEO-5 update pinned-prose tests with B6 Mechanical P1 prompt cleanups audit pinning tests leave tests
14 CEO CEO-6 TODOS.md entries for every deferral Mechanical P1 Prime Directive 7 chat-only deferrals
15 CEO CEO-7 anchors for every new failure state Mechanical P1 P3 promise A2/A4 only
16 CEO CEO-8 Windows tests named and curated Mechanical P1 spec review 1 unnamed
17 CEO CEO-9 A6 defaults ($2/trial ask, 3 rounds, units per kind) Taste (T2) P5 #3034 left values open other values
18 CEO CEO-10 B5 side note bounded to one fixture Mechanical P5 open-ended investigation investigate freely
19 CEO CEO-11 Tier 2 revert rule Mechanical P6 defines "ship if green" hold PR on any Tier 2 red
20 CEO CEO-12 structural free-text rule, no heredoc fallback Mechanical P1/P5 both voices heuristic lint + heredoc
21 CEO CEO-13 B7 out of static preamble; cheap gates listed Mechanical P1 consistency with #3012; AGENTS.md edit preamble
22 CEO CEO-14 split quota vs 429 Mechanical P1 transient 429 must not drop coverage 15 min 429 as quota
23 CEO CEO-15 nested sources + bash-only lint + auth probe Mechanical P1 spec review 2-3 SKILL.md-only list
24 CEO CEO-16 B6 changes router + injection + gstack CLAUDE.md Taste (T3) P1/P5 same wording in two places; never rewrite user files router only
25 CEO CEO-17 Tier 2 mechanics named Mechanical P5 implementable without questions leave open
26 CEO CEO-18 rework list for commits on the branch Mechanical P1 committed code contradicts accepted plan ignore drift
27 CEO CEO-19 B7/B9/B10/B11 tests and fallbacks Mechanical P1 spec review 3 untested
28 CEO CEO-20 B3 source-context exemption Mechanical P1 original rule fails its own control bare-identifier rule
29 CEO CEO-21 staged hook parse check before swap Mechanical P1 Codex 5: upgrade must not leave a broken hook live fresh-install-only check
30 CEO CEO-22 A5 acceptance scan Mechanical P1 close #3040 on evidence close on override bump
31 CEO CEO-23 "fixed" needs a named cause + explained failures Mechanical P1 both voices: 9/10 is not a rate 9/10 closes
32 CEO CEO-24 honest Windows claims + one e2e journey step Taste (T6) P1 Codex 6 claims only, no journey
33 CEO CEO-25 live skillOverrides check before B6 Mechanical P1 unproven premise assume
34 CEO CEO-26 comment on contributor PRs when commits land Mechanical P6 contributor channel comment only at merge
35 CEO CEO-27 B11 decided on first probe Mechanical P3 bounded scope open investigation
36 CEO CEO-28 keep sourcing, record reason, TODO subcommand Taste (T5) P5 already committed; skills call functions across a block convert to subcommands now
37 CEO CEO-4/CEO-11 A7 may stop red after 3 rounds; wave continues Taste (T1) P6 periodic case cannot hold the wave PR waits for A7 green
38 CEO Keep B6 in the wave Taste (T7) P1 real user bug #3018; panel already required defer B6 (Claude voice)
39 CEO Argument-array posting helper deferred Taste (T8) P5 new helper surface; structural rule covers P2 build now (Codex voice)
40 CEO Split A6 out of the wave / hotfix A1-A5 first USER CHALLENGE (UC1) n/a both voices recommend; not auto-decided (held for gate)
41 CEO 0H document approval Mechanical P6 documents reflect decisions revise
42 DX Product type Claude Code Skill (+ CLI) Mechanical P6 README/package.json/plan terms API/Library
43 DX Persona: macOS zsh founder with Codex Mechanical P6 README audience + reporters Windows-first persona
44 DX TTHW target Competitive (2-5 min) Mechanical P5 reachable with existing commands Champion (needs new command)
45 DX Magical moment via existing preflight line Mechanical P5 lowest-effort vehicle standalone readiness command
46 DX Mode DX POLISH Mechanical override autoplan fixes the mode EXPANSION/TRIAGE
47 DX DX-1 unified self-locate with loud failure Mechanical P1 Claude critical C1 verified in f0531a2 keep guessing fallbacks
48 DX DX-2 one token per state + absolute URLs Mechanical P5 both voices mixed spellings, repo-relative anchors
49 DX DX-3 quota retry-now bypass Mechanical P1 both voices no escape hatch
50 DX DX-4 A4 recovery recipe with /context-save Mechanical P1 both voices "start a new session" only
51 DX DX-5 refusals print cause, body path, exact command Mechanical P1 no dead ends "left unsent"
52 DX DX-6 auto-updater checks incoming hooks before fast-forward Mechanical P1 Codex critical verified in bin/gstack-session-update staged setup check only
53 DX DX-7 named controls + $25 total budget + opt-in Ubicloud Taste (T10) P5 both voices; values are judgment calls per-trial ask only
54 DX DX-8 one timed macOS zsh journey + CI probe check Mechanical P1 Pass 8 measures the approved clock unmeasured target
55 DX DX-9 verification commands + "What changes for you" Mechanical P1 both voices issue close without check
56 DX Keep X3 rejected; no parsed reset time Taste (T9) P5 fragile parsing; DX-3 covers recovery parse reset time (Codex)
57 DX Standalone readiness command deferred Mechanical P3 new command outside DX POLISH build now
58 DX A6 decoupling (Codex DX 3) USER CHALLENGE (UC1, repeated) n/a both voices again (held for gate)
59 Eng Scope accepted as-is; structure = Original arrangement Mechanical P2/P6 override: never reduce; split is UC1's call smaller arrangement
60 Eng ENG-1 rework inventory for every landed commit Mechanical P1 both voices; stale host outputs verified A1-A5 only
61 Eng ENG-2 honest setup-gate scope on the Claude checkout Mechanical P5 no staged swap exists there (verified) keep "previous install active" claim
62 Eng ENG-3 derived hook list + local-import bundle check Mechanical P1 both voices hand list, entry-only parse
63 Eng ENG-4 quota cache is the run-scoped skip Mechanical P5 fresh shells per block shell variable
64 Eng ENG-5 dash early return + helper_unavailable mode Mechanical P1 both voices; dash probe message after syntax error
65 Eng ENG-6 match Codex ERROR lines only Mechanical P1 echoed prompt false positive whole-stderr match
66 Eng ENG-7 zsh in Dockerfile.ci Mechanical P1 A1 text was wrong correct text only
67 Eng ENG-8 shell-safe identifiers + data transport Mechanical P1 Codex probe: check-ref-format accepts ; git validity alone
68 Eng ENG-9 keep landed B4 .source-path design + skew guard Taste (T13) P5 avoids junction delete hazard; already landed plan's junction
69 Eng ENG-9 B5 scoped to discovery + unsupported-transport outcome Mechanical P1 docker.ts accepts unix sockets only claim operation fixed
70 Eng ENG-10 B2 usage error before ensureServer Mechanical P1 both voices daemon boot
71 Eng ENG-11 B3 context classification replaces CEO-20 precedence Mechanical P1 both voices; Codex probes word-shape heuristic
72 Eng ENG-12 scripts/ship-measure.ts + admission budget + parallelism cap Mechanical P4/P1 both voices: runner gap prose-only loop
73 Eng ENG-13 seeded case cannot pass without the fix Mechanical P1 slots never reset as written
74 Eng ENG-14 verified write location + mktemp greptile JSON Mechanical P1 permission dead end, fixed /tmp paths assume /tmp
75 Eng ENG-15 anchor test resolves locally Mechanical P5 new anchors before main resolve against main
76 Eng Decline lint ratchet baseline Taste (T12) P1 free text gets zero tolerance ratchet (Claude E17)
77 Eng A6 decoupling (Codex eng recommendation) USER CHALLENGE (UC1, repeated) n/a raised in CEO, DX, Eng (held for gate)