* 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>
229 KiB
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:
- 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.
- 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 chainwith 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.mdordocs/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 (insideevalso bash never parses it), else the default install path. Apply it tobin/gstack-codex-probe(5 sites) andbin/gstack-egress-lib.sh.bin/gstack-pathsis executed, never sourced, so it keepsBASH_SOURCEunder 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 andzshunderzsh -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
sourceor.(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 onBASH_SOURCEin a sourced helper outside the shared idiom. CI installs zsh in the free lane and the Docker image; underCI=truea 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 andinsufficient_quotalines on a failed, non-timeout call (a plain rate-limit or HTTP 429 line is the separate, uncachedrate_limitedoutcome in CEO-14). It prints Codex's error line verbatim, maps toCODEX_MODE: quota_exhausted, and every caller treats it asunavailablemissing coverage (never a pass) and skips the remaining Codex calls in that run. Cached for 15 minutes under the same signature asMODEL_UNUSABLE. - Add reason codes
quota_exhaustedandrate_limitedtolib/gate-outcomes.ts, each with itsdocs/troubleshooting.mdanchor;classifyOutsideReviewrecognizes the same lines in a review's stderr so a mid-run quota error reads asunavailable (quota_exhausted)and a mid-run 429 asunavailable (rate_limited). - Tests: probe with a stub
codexthat prints the real message (from the issue) and exits 1; a stub that prints it and exits 0 must stayMODEL_OK; timeout keeps its inconclusive contract; every generated preflight block branches onquota_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
mktemppath, 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_largereason, 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 indocs/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, neveridentity; a real identity mismatch still yieldsidentity.
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.
bracesgets an[[IgnoredVulns]]entry withignoreUntilonly 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 chainwith 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.kvvalue 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.tsresolution) was fixed in v1.91.19.0. Defect 2 remains:server-node.mjsimportsplaywrightand the other externals, and the Windows runtime roots (copies, not symlinks) have no reachablenode_modules. Fix in setup for every runtime-root host: make the source checkout'snode_modulesreachable 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.exeat 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 childTRUSTED_PATHso 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_skillsconfig from the rendered router (C8, v1.91.19.0:omitDisabledSkillsinscripts/gen-skill-docs.ts). The remaining gap is skills turned off in Claude Code'sskillOverrides, 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 runsbash -non 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=offrestores it; the config-mismatch message says another session started the daemon with--headed/--proxyand offers both remedies. - B11. PR #3029: /sync-gbrain offers a
--dreambuild 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-defon 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 WSLbash. 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 --statusoutput. - 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.shsetsGSTACK_FREE_RETRY_FLAKY=1unconditionally 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, whenUBICLOUD_API_KEYis 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.tsaround line 341) keeps its own 32 MiB constant and stops throwing a plainErrorwith a hard-coded "32 MiB": it reportstoo_largewith the real size and cap, and so does theownedParentTranscript-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 yieldstoo_large, neveridentity; a real identity mismatch still yieldsidentity. 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 kindrule, so CEO-9's rule budget applies (10 trials, at least 9 passing). Each stub invocation claims the lowest free trial slot by creatingslots/<n>withmkdir, 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 afailure_causeandfailure_detailnaming 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 kindbehavior(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 bygstack-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.tsaround line 2117 andtest/helpers/autoplan-preconfigured-fixture.tsline 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_exhaustedandrate_limited, B2, B4, B5 and B8's setup refusal indocs/troubleshooting.md; A4too_largeand B1's shell-built-init denial indocs/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>ordocs/autoplan-guard-troubleshooting.md#<slug>), and a free test enumerates every such reference in the source tree and checks the anchor exists (extendingtest/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-stdinbrowse chaintest against a stub daemon; B4 a setup test that the runtime root resolvesplaywrightthrough the junction and that the Node load check runs against the runtime root; B5 a binding test forGSTACK_CSO_TRUSTED_DOCKER(accepted only from a non-user-writable directory, present inTRUSTED_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:
rulecases run 10 trials and need at least 9 passes;behaviorcases run 4 panels of 3 (12 trials) and need every panel to pass and at least 11 of 12 trials;judgecases 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'sscripts/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=@fileor--title "$(cat "$file")". A bash block creates the private path withmktemp, 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\\nsequences and triple backticks inside the current test-failure templates (ship/sections/tests.md, generated fromscripts/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-docsfreshness (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.tsandtest/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_quotamap toquota_exhausted(exit 4, cached 15 minutes). A plain HTTP 429 or rate-limit line maps to a separaterate_limitedoutcome (exit 0, like the inconclusive contract, with_GSTACK_CODEX_PROBE_STATE=rate_limited): never cached, printed verbatim, reported asCODEX_MODE: unverified (rate limited), and the review's own result still decides. Stub tests: usage-limit line with exit 1 givesquota_exhaustedand is cached;insufficient_quotagivesquota_exhausted; a bare 429 givesrate_limitedand is not cached; any of these lines with exit 0 staysMODEL_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 bybin/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 allowsBASH_SOURCEonly 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,[[ ... =~ ... ]]withBASH_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 thatbin/gstack-skill-start(around line 498) offers to new projects, plus gstack's ownCLAUDE.mdrouting 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
initargument 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 forbun "<SNAPSHOT_TOOL>" scope ...and for an unrelatedbun other.ts init. B2: with no arguments,chainreads 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 withcmd //c mklink /J, replaces a stale junction, leaves an existing realnode_modulesdirectory untouched and says so. B5 acceptsdocker.exeonly under%ProgramFiles%,%ProgramFiles(x86)%or%SystemRoot%(an allowlist, not an ACL check). B8 parses each shim with the interpreter named by its shebang (bash -nfor today's#!/usr/bin/env bashshims; Claude Code's/bin/shonly execs the shebang) and parses each TypeScript entry withbun build --no-bundleinto a temp directory; a failure refuses registration with the file and line. A5:bracesgetsignoreUntil = 2026-12-31only after confirming it is absent from the shippedlib/diagram-render/distbundle; 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-pathsstays untouched. A2 (597e483): its_QUOTA_SIG(bin/gstack-codex-probearound 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 namequota_exhaustedeverywhere (probe,lib/gate-outcomes.ts, docs anchorcodex-quota-exhausted), and add therate_limitedrow. 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-looptoo_largepaths 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,
ghmissing 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 stubgbraincovers 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.tomlagainst 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.ymlgains one journey step after setup: start the browse server from the installed runtime root and pipe a two-command flow intobrowse 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"inskillOverridesis 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 overfunctions_sourcefor simplicity, not version support. Turning the probe into an executed subcommand (gstack-codex-probe select-model execprinting 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$PWDwhen relative; else$GSTACK_ROOT/binwhenGSTACK_ROOTis exported and holds the helper (the variable the runtime prelude inscripts/resolvers/runtime-root.tsalready 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 inbin/gstack-codex-probeand the$(pwd)fallback inbin/gstack-egress-lib.sh(both in commitf0531a2) are removed. The class guard adds an unknown-shell case and a bare-name (source gstack-codex-probevia PATH) case. CEO-15's byte-for-byte lint applies after this rework; the two blocks committed inf0531a2differ today and must be unified first. Outside CI, a missing zsh printszsh not installed: install zsh to run the sourced-helper guard (CI requires it)and skips; underCI=trueit fails, as A1 already states. - DX-2 (one token per state, absolute doc links). The rate-limit state is spelled
rate_limitedeverywhere: probe outputCODEX_MODE: unverified (rate_limited), probe state, gate reason and docs anchorcodex-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 fullhttps://github.com/garrytan/gstack/blob/main/docs/<file>.md#<anchor>URL (the patternautoplan/bin/phase-publication-hook.tsalready 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_exhaustedoutput prints Codex's own line, when the gstack cache expires, and the retry-now command:GSTACK_CODEX_PROBE_RETRY=1bypasses the negative cache for one probe (documented in the anchor row, along with deleting<state root>/.codex-model-probe). The probe HINT committed in597e483("usage or rate limit", no anchor) is rewritten to match. A stub test covers recovery: cachedquota_exhausted, thenGSTACK_CODEX_PROBE_RETRY=1with a stub that now succeeds, givesMODEL_OKwithout changing model, config or credentials. - DX-4 (A4 recovery keeps the user's work). The
too_largedenial 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 withgit check-ref-format --branchinstead 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 thedocker.exepath 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 realnode_modulesin 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-updatefast-forwards the live checkout before setup runs (git merge --ff-onlyaround 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 (fromgit 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 existingBUN_HELDhold 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 manualgit pullfollowed by./setupcannot 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) andship_rerun_backend(localby default;ubicloudonly 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 indocs/TESTING_INTERNALS.mdand 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 caseunmeasuredin 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 showsCODEX_MODE: ready, and a second run with a stub quota response that showsCODEX_MODE: quota_exhaustedwith 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_backendreplaces CEO-1's automatic Ubicloud choice whenUBICLOUD_API_KEYis set; DX-5'sgit check-ref-format --branchreplaces CEO-12's example branch regex; DX-2'srate_limitedspelling replaces CEO-14'srate 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 printCODEX_MODEL: <model> (exec; source: ...), notinvalid. The CHANGELOG entry has a "What changes for you" list: the new messages and their links,quota_exhaustedskipping 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: "setGSTACK_DISABLE_GPU=offto enable WebGL (offis 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. B2cdc0fc7: see ENG-10. B34441c8f: see ENG-11. B6e103e57: regenerate every host (bun run gen:skill-docs --host all;.agents/,.factory/,.kiro/,.opencode/,.slate/still carry the old wording), updateCLAUDE.md:532andtest/helpers/autoplan-preconfigured-fixture.ts:7(CEO-5, CEO-16), run CEO-25's live check. B77bfe46b: matches. B8659b197: see ENG-2 and ENG-3. B923520a9:PR: unknownmust skip with the reason instead of dispatching, plus CEO-19's test. B105b06b7b: DX-9 wording check. B49618336and B123df850f: see ENG-9 for B4; B12 matches (CEO-8's Windows curation entry still owed). A5e77c532: the plan now records the two extra ignores it added (sprintf-js via roarr, katex in diagram-render) and theirignoreUntil = 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./setupruns 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 earlyexit 1before 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_pathargument 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=bunwith 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 existingBUN_HELDcheck). - 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_ROOTthat holds the helper. The shared block ends withreturn 1 2>/dev/null || exit 1after 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 runssource ... 2>/dev/null || true) keeps the source step's diagnostic and reports a distinctCODEX_MODE: helper_unavailablewith that line and its anchor, instead of falling through tonot_authed. Class-guard cases: dash (message, no syntax error), relative-path source followed bycd, 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 trailingERROR:/stream errorlines), not the whole stderr, which echoes the session prompt. Tests: an echoed prompt that contains "usage limit" andinsufficient_quotawith a non-quota exit staysexecution_failed; a 429 body that also carriesinsufficient_quotaresolves toquota_exhausted(quota takes precedence over rate limit). - ENG-7 (Dockerfile.ci installs zsh).
.github/docker/Dockerfile.ciadds 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 --branchacceptsfix;idandfix`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'smklink /Jjunction, 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./setupfix. 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 currentwindows-setup-e2e.ymlrunsbun run buildbut never./setup), then the installedbrowsefrom an unrelated directory piping a two-commandchainflow. 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". Becauselib/cso/docker.tsaccepts onlyunix:///endpoints, B5 is scoped to discovery: withdocker.exebound, /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 beforeensureServer(). 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.tsoptions) 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.tsfile, a quoted literal, a brace-wrapped literal, a literal concatenated to a property read. Controls that must not flag:session: SessionState,andkey={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 asrunCaseDiagnosiswould 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'sship_measure_budget_usdis 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 drivescripts/ship-measure.tswith 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.jsonand/tmp/greptile_top.jsonmove tomktemppaths. - 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/...#anchorURLs 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/main5885157a9, v1.91.29.0). Branchgarrytan/fix-wave-oct6; the only change on it is this untracked plan file. - Design doc:
gstack-design-doc-findreturns this plan itself, so it is the problem/constraints source. No/office-hoursdoc; 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-reviewsections,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-searchreturned 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-preservedreds), 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.$0andfunctions_sourceare the known wrong answers, matching the plan's rejections. Layer 3: nothing contradicts the conventional idiom.
0A. Premise challenges
- "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'printsModule not found "/../scripts/resolve-codex-generation-model.ts"andCODEX_MODEL: invalid.bin/gstack-egress-lib.shunder zsh from/tmpresolves_gstack_egress_lib_dir=/tmp. Only three files useBASH_SOURCE(bin/gstack-codex-probe5 sites,bin/gstack-egress-lib.sh,bin/gstack-paths); skills sourcegstack-codex-probe(33 rendered sites) andgstack-egress-lib.sh(15).gstack-pathsis executed, not sourced, by skills, so its fix is defensive. Do-nothing cost: every outside pass on the default macOS shell reportsmodel_unusableand the user loses cross-model coverage without knowing why. - "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_INCONCLUSIVEand 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. - "Reply templates run backticks as shell" (A3). Valid.
review/greptile-triage.md:102,108andscripts/resolvers/preamble/generate-test-failure-triage.ts:74-75confirmed. TODOS.md already carries "placeholder convention for generated bash" (P3); the A3 lint is the first enforced piece of it. - "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:555throwsOwnedReadError('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). - "The root router routes to skills the user disabled" (B6). Partly stale. Main already removes skills disabled in gstack's own
disabled_skillsfrom the rendered router (omitDisabledSkills,scripts/gen-skill-docs.ts:171-190, v1.91.19.0). What remains: skills turned off in Claude Code'sskillOverrides(gstack cannot see them at render time) and the "When in doubt, invoke the skill" wording, whichtest/gen-skill-docs.test.ts:2117andtest/helpers/autoplan-preconfigured-fixture.ts:7pin. Plan text corrected by baseline edit. - "#3033 overlaps only cli.ts, cso/process.ts and redact-patterns.ts". False.
pr3033-files.txtalso lists.github/docker/Dockerfile.ci,.github/workflows/free-tests.yml,docs/troubleshooting.md,docs/TESTING_INTERNALS.md,review/SKILL.md.tmpl,TODOS.mdand the release files, all of which this wave edits. Plan text corrected by baseline edit. - "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.
- "A7's timeout is a new defect". Unproven. TODOS.md records five earlier
auto-decide-preservedreds caused by the skipped Step 0E handoff line, fixed in v1.91.19.0 bygstack-ceo-mode-handoff. The first step must replay the captured run (37272185151) against that known class before paying for trials (CEO-D7). - "B8: a hook that does not parse blocks every tool call". Valid, low frequency, high impact. The shims are
#!/usr/bin/env bashfiles 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. - A5 reachability. Valid with a correction.
fast-uriandhonoarrive only through the dev dependency@anthropic-ai/claude-agent-sdk->@modelcontextprotocol/sdk(eval harness, not shipped runtime);ip-addressis runtime throughsocks(browse proxy). Overrides remain the right fix for all three; the browse proxy test run is the one that matters forip-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:
- 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).
- 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).
- 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.shsetsGSTACK_FREE_RETRY_FLAKY=1unconditionally 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, whenUBICLOUD_API_KEYis 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.tsaround line 341) keeps its own 32 MiB constant and stops throwing a plainErrorwith a hard-coded "32 MiB": it reportstoo_largewith the real size and cap, and so does theownedParentTranscript-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 yieldstoo_large, neveridentity; a real identity mismatch still yieldsidentity. 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 kindrule, so CEO-9's rule budget applies (10 trials, at least 9 passing). Each stub invocation claims the lowest free trial slot by creatingslots/<n>withmkdir, 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 afailure_causeandfailure_detailnaming 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 kindbehavior(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 bygstack-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.tsaround line 2117 andtest/helpers/autoplan-preconfigured-fixture.tsline 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_exhaustedandrate_limited, B2, B4, B5 and B8's setup refusal indocs/troubleshooting.md; A4too_largeand B1's shell-built-init denial indocs/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>ordocs/autoplan-guard-troubleshooting.md#<slug>), and a free test enumerates every such reference in the source tree and checks the anchor exists (extendingtest/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-stdinbrowse chaintest against a stub daemon; B4 a setup test that the runtime root resolvesplaywrightthrough the junction and that the Node load check runs against the runtime root; B5 a binding test forGSTACK_CSO_TRUSTED_DOCKER(accepted only from a non-user-writable directory, present inTRUSTED_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:
rulecases run 10 trials and need at least 9 passes;behaviorcases run 4 panels of 3 (12 trials) and need every panel to pass and at least 11 of 12 trials;judgecases 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'sscripts/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=@fileor--title "$(cat "$file")". A bash block creates the private path withmktemp, 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\\nsequences and triple backticks inside the current test-failure templates (ship/sections/tests.md, generated fromscripts/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-docsfreshness (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.tsandtest/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_quotamap toquota_exhausted(exit 4, cached 15 minutes). A plain HTTP 429 or rate-limit line maps to a separaterate_limitedoutcome (exit 0, like the inconclusive contract, with_GSTACK_CODEX_PROBE_STATE=rate_limited): never cached, printed verbatim, reported asCODEX_MODE: unverified (rate limited), and the review's own result still decides. Stub tests: usage-limit line with exit 1 givesquota_exhaustedand is cached;insufficient_quotagivesquota_exhausted; a bare 429 givesrate_limitedand is not cached; any of these lines with exit 0 staysMODEL_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 bybin/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 allowsBASH_SOURCEonly 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,[[ ... =~ ... ]]withBASH_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 thatbin/gstack-skill-start(around line 498) offers to new projects, plus gstack's ownCLAUDE.mdrouting 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
initargument 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 forbun "<SNAPSHOT_TOOL>" scope ...and for an unrelatedbun other.ts init. B2: with no arguments,chainreads 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 withcmd //c mklink /J, replaces a stale junction, leaves an existing realnode_modulesdirectory untouched and says so. B5 acceptsdocker.exeonly under%ProgramFiles%,%ProgramFiles(x86)%or%SystemRoot%(an allowlist, not an ACL check). B8 parses each shim with the interpreter named by its shebang (bash -nfor today's#!/usr/bin/env bashshims; Claude Code's/bin/shonly execs the shebang) and parses each TypeScript entry withbun build --no-bundleinto a temp directory; a failure refuses registration with the file and line. A5:bracesgetsignoreUntil = 2026-12-31only after confirming it is absent from the shippedlib/diagram-render/distbundle; 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-pathsstays untouched. A2 (597e483): its_QUOTA_SIG(bin/gstack-codex-probearound 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 namequota_exhaustedeverywhere (probe,lib/gate-outcomes.ts, docs anchorcodex-quota-exhausted), and add therate_limitedrow. 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-looptoo_largepaths 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,
ghmissing 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 stubgbraincovers 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.tomlagainst 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.ymlgains one journey step after setup: start the browse server from the installed runtime root and pipe a two-command flow intobrowse 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"inskillOverridesis 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 overfunctions_sourcefor simplicity, not version support. Turning the probe into an executed subcommand (gstack-codex-probe select-model execprinting 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; A1f0531a2, A3a853e41, A2597e483landed on the branch during this review), other quoting shapes,rate_limitedexit 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-pathsis 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, A1gstack-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(sha256097e02e4...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 (watchdog2e1d3e7,OWNED_TRANSCRIPT_MAX_BYTESalready 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,skillOverridesreflected 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_limitedrow- 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 -15andgit 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.tomlbefore 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(sha256f71662d4...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 onf0531a2; H1 rate-limit token spelled three ways, norate_limitedanchor; H2 no escape from the 15-minute quota cache, committed HINT still says "usage or rate limit" with no anchor; H3too_largemessage 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; M8GSTACK_DISABLE_GPU=offdouble 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); afterCODEX_MODEL: <model> (exec; ...), or on an unknown shellgstack: 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 doctoror 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_ROOTfallback, 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_limitedtoken, 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$PWDwhen relative; else$GSTACK_ROOT/binwhenGSTACK_ROOTis exported and holds the helper (the variable the runtime prelude inscripts/resolvers/runtime-root.tsalready 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 inbin/gstack-codex-probeand the$(pwd)fallback inbin/gstack-egress-lib.sh(both in commitf0531a2) are removed. The class guard adds an unknown-shell case and a bare-name (source gstack-codex-probevia PATH) case. CEO-15's byte-for-byte lint applies after this rework; the two blocks committed inf0531a2differ today and must be unified first. Outside CI, a missing zsh printszsh not installed: install zsh to run the sourced-helper guard (CI requires it)and skips; underCI=trueit fails, as A1 already states. - DX-2 (one token per state, absolute doc links). The rate-limit state is spelled
rate_limitedeverywhere: probe outputCODEX_MODE: unverified (rate_limited), probe state, gate reason and docs anchorcodex-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 fullhttps://github.com/garrytan/gstack/blob/main/docs/<file>.md#<anchor>URL (the patternautoplan/bin/phase-publication-hook.tsalready 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_exhaustedoutput prints Codex's own line, when the gstack cache expires, and the retry-now command:GSTACK_CODEX_PROBE_RETRY=1bypasses the negative cache for one probe (documented in the anchor row, along with deleting<state root>/.codex-model-probe). The probe HINT committed in597e483("usage or rate limit", no anchor) is rewritten to match. A stub test covers recovery: cachedquota_exhausted, thenGSTACK_CODEX_PROBE_RETRY=1with a stub that now succeeds, givesMODEL_OKwithout changing model, config or credentials. - DX-4 (A4 recovery keeps the user's work). The
too_largedenial 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 withgit check-ref-format --branchinstead 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 thedocker.exepath 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 realnode_modulesin 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-updatefast-forwards the live checkout before setup runs (git merge --ff-onlyaround 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 (fromgit 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 existingBUN_HELDhold 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 manualgit pullfollowed by./setupcannot 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) andship_rerun_backend(localby default;ubicloudonly 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 indocs/TESTING_INTERNALS.mdand 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 caseunmeasuredin 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 showsCODEX_MODE: ready, and a second run with a stub quota response that showsCODEX_MODE: quota_exhaustedwith 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_backendreplaces CEO-1's automatic Ubicloud choice whenUBICLOUD_API_KEYis set; DX-5'sgit check-ref-format --branchreplaces CEO-12's example branch regex; DX-2'srate_limitedspelling replaces CEO-14'srate 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 printCODEX_MODEL: <model> (exec; source: ...), notinvalid. The CHANGELOG entry has a "What changes for you" list: the new messages and their links,quota_exhaustedskipping 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: "setGSTACK_DISABLE_GPU=offto enable WebGL (offis 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'tstill present aftere103e57changedSKILL.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(landed659b197) —_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(landed23520a9) —`PR: none` → skip the dispatch ... Otherwise dispatchdispatches onPR: 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.tsreadsAsCodeName(landed4441c8f) — 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(landede77c532) — 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(sha256190bb276...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; E4GSTACK_ROOTis set, not exported; E5 dash dies on later syntax before DX-1's message (probed:Syntax error: redirection unexpectedat line 76); E6 quota matching reads the echoed prompt; E7 junction delete hazard; E8 B5 trusts env vars and claims an ACL property; E9readFileSync(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_*.jsonpaths; E17 lint as ratchet; E18 budget has no owner script; E19 DX-6 ordering afterBUN_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)
setuphook gate (landed) checks live files and exits before registering anything — ENG-2/ENG-3. - [P1] (confidence: 9/10)
scripts/lib/paid-report.tsrunCaseDiagnosiskeeps the behavior threshold at 2 while expanding trials (Codex probe) — A6 needs its own runner, ENG-12. - [P2] (confidence: 8/10) B4 landed
.source-pathdesign 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.tsaccepts onlyunix:///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.tsreadsAsCodeNameexemptsAPI_KEY=VelvetRiverOrbitSunset;(Codex probe) — ENG-11. - [P2] (confidence: 8/10)
bin/gstack-codex-probe:37-41andbin/gstack-egress-lib.sh:52-55duplicate 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.mdfixed/tmp/greptile_*.jsonpaths — 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 listquota_exhaustedandrate_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 archiveplus a fewbun buildruns 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:243still 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
- Surfaced by: Scope Challenge —
- 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.tswith 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:
- 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.
- 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.
- Executed-subcommand Codex probe (CEO-28) — removes the caller-shell dependency. Effort M. P2.
- Argument-array posting helper (CEO-12 stronger form) — Effort M. P2.
- Native Windows Docker transport for /cso (ENG-9) — named-pipe endpoint support in
lib/cso/docker.ts. Effort M. P2. - Standalone readiness command (DX:
gstack doctoror a Codex row in./setup --status) — Effort S/M. P3. - Per-reason telemetry — already in TODOS (gate-outcome telemetry, P3); no new entry.
- 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. B2cdc0fc7: see ENG-10. B34441c8f: see ENG-11. B6e103e57: regenerate every host (bun run gen:skill-docs --host all;.agents/,.factory/,.kiro/,.opencode/,.slate/still carry the old wording), updateCLAUDE.md:532andtest/helpers/autoplan-preconfigured-fixture.ts:7(CEO-5, CEO-16), run CEO-25's live check. B77bfe46b: matches. B8659b197: see ENG-2 and ENG-3. B923520a9:PR: unknownmust skip with the reason instead of dispatching, plus CEO-19's test. B105b06b7b: DX-9 wording check. B49618336and B123df850f: see ENG-9 for B4; B12 matches (CEO-8's Windows curation entry still owed). A5e77c532: the plan now records the two extra ignores it added (sprintf-js via roarr, katex in diagram-render) and theirignoreUntil = 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./setupruns 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 earlyexit 1before 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_pathargument 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=bunwith 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 existingBUN_HELDcheck). - 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_ROOTthat holds the helper. The shared block ends withreturn 1 2>/dev/null || exit 1after 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 runssource ... 2>/dev/null || true) keeps the source step's diagnostic and reports a distinctCODEX_MODE: helper_unavailablewith that line and its anchor, instead of falling through tonot_authed. Class-guard cases: dash (message, no syntax error), relative-path source followed bycd, 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 trailingERROR:/stream errorlines), not the whole stderr, which echoes the session prompt. Tests: an echoed prompt that contains "usage limit" andinsufficient_quotawith a non-quota exit staysexecution_failed; a 429 body that also carriesinsufficient_quotaresolves toquota_exhausted(quota takes precedence over rate limit). - ENG-7 (Dockerfile.ci installs zsh).
.github/docker/Dockerfile.ciadds 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 --branchacceptsfix;idandfix`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'smklink /Jjunction, 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./setupfix. 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 currentwindows-setup-e2e.ymlrunsbun run buildbut never./setup), then the installedbrowsefrom an unrelated directory piping a two-commandchainflow. 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". Becauselib/cso/docker.tsaccepts onlyunix:///endpoints, B5 is scoped to discovery: withdocker.exebound, /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 beforeensureServer(). 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.tsoptions) 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.tsfile, a quoted literal, a brace-wrapped literal, a literal concatenated to a property read. Controls that must not flag:session: SessionState,andkey={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 asrunCaseDiagnosiswould 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'sship_measure_budget_usdis 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 drivescripts/ship-measure.tswith 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.jsonand/tmp/greptile_top.jsonmove tomktemppaths. - 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/...#anchorURLs 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) |