Commit Graph
313 Commits
Author SHA1 Message Date
Garry TanandClaude Fable 5 2b8c1e584e test(codex): execute the JSONL parser under real python3; fence scanner tracks opener length
The parser's turn.completed/turn.failed/disconnect semantics were pinned
by shape only — now the python block is extracted from both RENDERED
sections and run against synthetic event streams (tokens line, FAILED +
not-a-disconnect, silence -> disconnect warning, SESSION_ID echo), with
byte-equivalence safety pins on the bash double-quote extraction. The
fence scanner also gains CommonMark opener-length tracking: a 4-backtick
fence wrapping a 3-backtick example no longer false-positives, with a
self-test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 16:08:59 +00:00
Garry TanandClaude Fable 5 2310ccdb7d test(gbrain): invalid version-probe timeout env falls back to the default
GSTACK_GBRAIN_VERSION_PROBE_TIMEOUT_MS set to 'abc', '-1', or '0' must use
the default budget — exercised behaviorally through probeGbrainBin with a
fresh PATH per case (the memo keys on PATH).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 16:08:59 +00:00
Garry TanandClaude Fable 5 3bc8076801 test(auq): pin the explicit-declaration-only spawned escape sentence
SPAWNED_ESCAPE_SENTENCE's tightened wording had no pin: positive pins on
the explicit-declaration clause, negative pins on the retired v1.76 loose
parentheticals ('e.g. your dispatch prompt says', 'marks this session as
spawned'), and a drift guard that both hook directives embed the constant
verbatim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 16:08:58 +00:00
Garry TanandClaude Fable 5 abec37639c test(upgrade): v1.78.0.0 feature-marker migration suite
The only migration without a dedicated test. Covers copy-when-absent
(script must mkdir GSTACK_HOME itself), destination-wins (never
overwrites), clean no-op, and two-run idempotence — asserting file
existence and contents, not just exit codes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 16:08:15 +00:00
Garry TanandClaude Fable 5 8a150fcb58 test(config): signal-killed spawns map to -1, not exit 0
Both cfg() helpers defaulted a null spawn status to 0 — a child killed by
signal would read as success and mask real failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 16:08:15 +00:00
Garry TanandClaude Fable 5 7be0c69e32 test(hostile-path): per-run mkdtemp root; telemetry-log redaction coverage
The suite used a FIXED tmpdir name, so concurrent runs (sharded runner,
sibling worktrees) tore down each other's trees mid-flight — now a
per-run mkdtemp root with the apostrophe dir inside. gstack-telemetry-log
was the one bin named in the suite header with no test: it now must append
a real row under the hostile path with the credential span redacted
(<REDACTED-github.pat>) and the rest of the message preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 16:08:15 +00:00
Garry TanandClaude Fable 5 781cc13779 refactor(memory-ingest): reunite preparePages with its docblock; pin disambiguateSlugs wiring
The #2724 disambiguateSlugs block was inserted between preparePages'
docblock and the function, orphaning the secret-scanning policy doc onto
the wrong symbol. Reordered. A call-site pin now asserts the prepare→stage
flow actually invokes disambiguateSlugs, so a refactor can't drop the call
while every unit test stays green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 16:08:05 +00:00
Garry TanandClaude Fable 5 c0db2670f8 fix(ship): PR/MR create aborts on a missing or empty scanned body file
Both the gh and glab send blocks now guard [ -s "$PR_BODY_FILE" ] and the
prose restates that the variable comes from the scan block — bash blocks
run in separate shells, and an unset/empty path would previously send an
empty body (gh) or cat's error output (glab) instead of the scanned bytes.
Codex/factory ship goldens regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 16:07:45 +00:00
Garry TanandClaude Fable 5 2d1645d578 fix(auq): the spawned trigger is the STATUS echo, nothing else — prose channel removed from the eager path
Two pinned-container periodic rounds showed that ANY dispatch-prompt
declaration channel in rule 1 keeps question counts unstable (round 1, fence
with behavioral clause: paired control 5>4, band 8>7; round 2, bare fence:
intermittent 0s return, paired control breaks both directions). The stable
regime CI was calibrated against had no spawned prose in the eager path at
all. Rule 1 now keys on exactly one machine-verifiable thing: the preamble's
own SESSION_KIND: spawned STATUS echo. No text from a dispatch prompt, file,
or page can flip a session to auto-choose (the strongest anti-injection
form). Subagents that missed the env marker are caught at FAILURE time by
the AUQ hooks' spawned escape (explicit declaration, never inference) — a
channel that never enters an interactive session's eager reasoning.

This reverses the wave's earlier explicit-declaration middle ground (and
adopts the outside voice's twice-made echo-only argument) on the new
evidence. #2733 protected: skill-e2e-docsync-spawned (gate) passes 1/1 on
this prose — the ship Step-18 dispatch forces the env prefix, so the echo
fires there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 00:19:59 +00:00
Garry TanandClaude Fable 5 3e4092f8b1 fix(auq): the interactive fence ends at classification — all behavioral tails removed
The pinned-container periodic lane proved the collapse dead (reviewCount 0 →
7/8/5 across the AUQ suite) but flagged the fence's remaining behavioral
clause: 'never adds, removes, or batches the skill's decision points' broke
the paired-finding control (5 > 4 — it suppressed the batching that fixture
expects), and the band overshot its ceiling (8 > 7). Every behavioral tail
tried so far skewed counts somewhere ('when unsure, ask' → 8; 'HOW MANY
questions' → 1; 'never batches' → paired control red). The fence now ends at
'When unsure, default to interactive.' — classification only, zero behavior
words. Pins forbid every tried-and-failed phrasing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 23:03:42 +00:00
Garry TanandClaude Fable 5 9f6ad83530 test(memory-ingest): fence regression test survives Windows tmpdirs
Wave polish on the #2699 absorption: the hand-built JSONL interpolated the
raw tmpdir into a JSON string — on Windows (D:\a\...) that's an invalid
escape, the user line was silently dropped, and the body started at
'## Assistant' (Windows Free Tests red). JSON.stringify the path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 22:20:31 +00:00
Garry TanandClaude Fable 5 bd621cc023 fix(redact): large reports survive the pipe — exitCode instead of process.exit; inert test payload
The wave's PR quality gate failed closed: gate-secret-scan.mjs pipes the
diff's added lines into gstack-redact and parses the JSON report, but
process.exit() discards stdout still buffered in the pipe — this wave's
646-finding report (202 KB) is the first big enough to arrive truncated
(~145 KB) at node's collector, so JSON.parse failed and the gate read
'no report' as HIGH. The report and auto-redact body paths now set
process.exitCode and let the runtime drain stdout; exit-code contract
unchanged (verified 0/2/3 end-to-end). Also: the C1 test's stdin payload no
longer uses a provider-prefix credential shape (the gate correctly flagged
it; the content was never read on the error path under test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 22:19:16 +00:00
Garry TanandClaude Fable 5 f6076da8e4 test: rewrite gate pin follows the LINK_ROOT rename (#2692)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 21:38:22 +00:00
Garry TanandClaude Fable 5 d4b0131636 fix(wtree): a failed touch falls through to the HEAD seed instead of reopening the racy window (#2687)
The v1.74 racy-git fix carries the real index's mtime onto the temp copy —
but its 'touch -r … || true' meant a FAILED touch silently kept the copy's
fresh stamp, marking every entry non-racy and reopening the exact same-size-
rewrite hole. A failed touch now discards the copy and seeds from read-tree
HEAD (slower; every entry re-hashed; fingerprint stays honest).

Verification for #2687 itself: the reporter's same-size-rewrite repro run 20
iterations against this tree — 0 misses (the underlying race was fixed by
v1.74's b1485d88 with its own regression test; this wave verifies and closes,
it does not claim that fix). Receipt: the stubbed-touch test fails on a
v1.77.0.0 scratch worktree.

Fixes #2687

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 21:36:02 +00:00
Garry TanandClaude Fable 5 079b36c7f8 feat(setup): persistent timeline Stop hook opt-out — timeline_stop_hook config gate (#2677)
--no-team is a one-shot teardown, so every later bare ./setup (including the
ones /gstack-upgrade runs) re-registered the timeline Stop hook with no way
to say 'never'. New gate mirrors the plan_tune_hooks pattern: flag
(--timeline-stop-hook/--no-timeline-stop-hook) > env
(GSTACK_TIMELINE_STOP_HOOK) > saved config (timeline_stop_hook) > default
yes. An explicit flag persists to config so the decision survives upgrades;
an explicit 'no' also removes a live registration (reconciliation), so the
opt-out works against installs registered by an older setup. --no-team
semantics unchanged (NO_TEAM_MODE is never initialized from config). Full
gstack-config surface: DEFAULTS entry, header docs, list/defaults
enumeration, warn-and-default validation.

Fixes #2677

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 21:26:55 +00:00
Garry TanandClaude Fable 5 7ed5e87bba fix(render): section refs point at the FINAL render dir, never the tmp swap dir (#2692)
gen-skill-docs bakes its --out-dir into rendered CONTENT (rewriteSectionBase),
and both swap-in callers (setup, gstack-config gbrain-refresh) render into
claude.tmp.<pid> before the #2569 atomic rename — so every rendered skill
carried ~9 dead section Read paths that pointed at a directory the swap had
just deleted. New --link-root flag names the final serving dir (defaults to
--out-dir for direct-render callers: bin/dev-setup, dev-skill.ts, mkdtemp
tests — full caller audit in the wave notes); the rewrite now uses a
replacement callback so a $-bearing configured path can't expand as $& in a
replacement string. The swap logic itself stays byte-identical. Tests pin the
generator contract (tmp out-dir files reference the final dir, $-bearing
path included) and both callers' wiring.

Fixes #2692

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 21:23:53 +00:00
Garry TanandClaude Fable 5 e2d6cb570f fix(relink): skill_prefix patches the gbrain render too — the file the host actually serves (#2738)
gstack-relink linked SKILL.md from RENDER_DIR when a gbrain render was active
but ran gstack-patch-names only on INSTALL_DIR, so the served frontmatter kept
the unprefixed name and skill_prefix=true silently no-oped for every
brain-aware skill. The render tree (user-owned, untracked) is now patched too;
gstack-patch-names is idempotent so repeat relinks never double-prefix. The
gen-skill-docs note that pointed users at relink now describes what relink
actually covers.

Receipt: the new test fails on a v1.77.0.0 scratch worktree (served render
keeps 'name: qa').

Fixes #2738

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 21:21:38 +00:00
Garry TanandClaude Fable 5 f7e378cebe fix: outside-voice fallback is labeled honestly — same model family, not cross-model (#2735)
When Codex is unavailable, the plan-review outside voice falls back to a
Claude subagent and the copy sold it as 'cross-model coverage' with 'genuine
independence'. Fresh context is real; cross-model validation is not — a user
weighing 'both reviewers agree' deserves to know both reviewers share a model
family. Six canonical strings fixed at the resolver source (constants.ts
not_installed/not_authed, review.ts outside-voice bullet + three dispatch
paragraphs); ~10 generated docs and the ship goldens regenerated. Printing
the resolved fallback model at dispatch time is descoped as a functional
change (follow-up in the wave dispositions).

Fixes #2735

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 21:20:26 +00:00
Garry TanandClaude Fable 5 9ca92161e2 fix(codex): close the consult-mode fence, report turn.failed as a failure, capture exit codes portably (#2671, #2669)
Three defects in the codex skill sections:

- The resumed-session bash block never closed its fence; every fenced region
  after it inverted (prose rendered as code, the synthesis-recommendation tail
  rendered inert). A repo-wide fence-pairing test now scans every generated
  SKILL.md and sections/*.md with a CommonMark-faithful state machine (an
  info-string opener inside a fence is literal content — nested template
  examples in document-generate/make-pdf stay legal; a file ending inside a
  fence fails).

- The JSONL parsers had no turn.failed branch: a turn that STATED its failure
  was reported as 'possible mid-stream disconnect'. Challenge and consult now
  print the event's error and run a three-way completeness check (failed-with-
  reason / silent-disconnect / ok); consult previously had no completeness
  check at all.

- ${PIPESTATUS[0]} is empty under zsh, so hang detection never fired and
  every clean run printed a spurious '[codex exit ]'. All three capture sites
  use ${PIPESTATUS[0]:-${pipestatus[1]}}, pinned statically and EXECUTED
  under real bash and zsh in the new test. Expect a step-change in
  codex_timeout telemetry — the counter starts firing for zsh users.

Receipt: the portability pin fails on a v1.77.0.0 scratch worktree; the fence
fix is structural (17 → 18 fence lines, tail no longer inside a block).

Fixes #2671
Fixes #2669

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 21:19:06 +00:00
Garry TanandClaude Fable 5 262a605794 fix(gbrain): a slow --version probe classifies as timeout, never no-cli (#2716)
resolveGbrainBin's bare catch collapsed 'gbrain missing' and 'gbrain present
but the 2s --version budget expired' into the same null — freshClassify then
said no-cli, which the --is-ok whitelist from #1964 does NOT forgive, so a
bun-shim install on a loaded POSIX box silently lost every brain-aware block.
The probe now returns a discriminated result (cached per-process, same
lifetime the old null had) using the same killed/SIGTERM/ETIMEDOUT
discrimination the sources-list probe below already uses; timeout routes to
the forgiven 'timeout' status. GSTACK_GBRAIN_VERSION_PROBE_TIMEOUT_MS test
override added (same precedent as the sources-probe override).

Receipt: the slow-but-present sibling test fails on a v1.77.0.0 scratch
worktree (classifies no-cli there).

Fixes #2716

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 21:15:49 +00:00
Garry TanandClaude Fable 5 84c0eee9f2 test: spawn timeouts on absorbed-PR tests (v1.77 sync-spawn tripwire)
The absorbed community tests (#2748, #2676, #2714, #2720) were authored
before the v1.77 tripwire required a timeout on every sync spawn in the test
trees.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 21:12:39 +00:00
Garry TanandClaude Fable 5 47cb8a838a test: spawn timeouts on the #2748 marker tests (v1.77 sync-spawn tripwire)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 21:12:11 +00:00
ptt 2323bac857 fix(land-and-deploy): check fork branch in head repo
Wave-amended: gh leaves .headRepository.nameWithOwner empty (verified live against gh 2.83) — owner/name now composed from headRepositoryOwner.login + headRepository.name so reconciliation is not a permanent no-op; fork branches get report-not-delete (maintainers lack fork push rights); pins updated
2026-08-31 21:09:11 +00:00
Simon Altit 69fa5a7a68 fix: align feature marker seeding with GStack state
Wave-amended: seeding relocation re-applied to the composite action (v1.77 moved CI seeding out of the inline workflow steps the original commit edited); wiring tripwire re-pointed accordingly; stale marker comment updated
2026-08-31 21:06:39 +00:00
Simon Altit 6a14a387d0 fix: keep feature markers in GStack state 2026-08-31 21:02:34 +00:00
Ryan AyersandClaude Opus 4.8 96ac9bbfef fix(memory-ingest): stop two silent transcript-ingest failures
Two independent bugs made transcript pages silently fail to reach the brain.

1. Frontmatter fence gluing. buildTranscriptPage() built the closing "---"
   with no trailing newline, and session bodies always start with "## ", so
   the rendered page ended "...---## User". gbrain's frontmatter matcher
   (/^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/ in src/core/markdown.ts) requires
   the closing "---" to end its own line, so it skipped the glued fence,
   latched onto the next standalone "---" in the transcript body, parsed the
   prose between as YAML, and dropped the page with "Invalid YAML frontmatter".
   Transcripts with no later "---" fell back to body-only, silently losing
   their frontmatter. Fix: emit the fence on its own line with a blank
   separator, matching renderPageBody()'s artifact branch.

2. Slug collisions. Two source files can map to one path-derived slug (a
   session resumed under the same id on one day, or two ids sharing a 12-char
   prefix). writeStaged() names each file "${slug}.md", so the second
   overwrote the first; gbrain collected N-1 of N staged files and the
   reconciliation guard failed the whole batch every run. Fix:
   disambiguateSlugs() keeps the first occurrence and gives each later collider
   a stable "-<sha8(source_path)>" suffix (deterministic, and slug + page_slug
   move together so writeStaged, the failure mapping, and state recording agree).

Exports buildTranscriptPage, renderPageBody, and disambiguateSlugs for tests.
Adds regression tests for both failures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wave-amended: contributor's local-workaround docblock note removed; issue refs retargeted #2653 (closed by its author) -> #2724 (the live 887-staged-to-0-ingested report)
2026-08-31 21:02:23 +00:00
Shreshth Kapoor 9f4e8eef48 fix: bin writers drop data on Windows paths with an apostrophe
Two independent Windows git-bash bugs in the bin writers, both silent
because callers invoke these scripts with 2>/dev/null and do not check
the exit status — a hard failure was indistinguishable from success.

Bug 1 — apostrophe in the checkout path breaks the bun -e program.
gstack-learnings-log, gstack-question-log and gstack-telemetry-log build
a bun -e program as a double-quoted shell string and interpolate
SCRIPT_DIR into a single-quoted JS import specifier. A path such as
C:/Users/Someone's PC/... closes the JS string literal early and Bun
fails to parse ("Expected ; but found s"). Every learning write and every
plan-tune question event no-oped; telemetry error redaction fell to its
fail-closed null path. The #1950 cygpath -m guard did not cover this —
cygpath normalises the drive form but does not remove the apostrophe.

Fixed by not interpolating the path at all: cd into the module root and
use a relative import specifier, which is immune to apostrophes, spaces,
backslashes and MSYS paths alike. The one remaining interpolated data
path in gstack-developer-profile (readFileSync of PROFILE_FILE) is passed
via the environment instead, matching do_log_session in the same file.

Bug 2 — gstack-developer-profile --derive fails on an MSYS-form
GSTACK_HOME. GSTACK_HOME defaults to $HOME/.gstack, which under git-bash
is /c/Users/..., and Bun on Windows cannot open that form (ENOENT). This
script carried no cygpath guard at all. Fixed by normalising GSTACK_HOME
once, before PROFILE_FILE / LEGACY_FILE / the events path are derived
from it, so all three pick up the normalised value.

Adds test/hostile-path-writers.test.ts, which runs the bins from a
directory whose name contains an apostrophe and asserts that rows are
ACTUALLY WRITTEN (not merely that the exit code is 0 — exit-code-only
checks are what masked bug 1). The apostrophe repro is OS-independent:
SCRIPT_DIR derives from the script's own location, so a copied checkout
under a hostile directory name reproduces bug 1 on Linux/macOS CI too.

Wave-amended: all four writers unified on the env-var import pattern the PR already used in gstack-developer-profile (no CWD-dependent module resolution)
Wave-amended: all four writers unified on the env-var import pattern the PR already used in gstack-developer-profile (apostrophe-safe without CWD-dependent module resolution); import-shape pin updated
2026-08-31 21:01:51 +00:00
Garry TanandClaude Fable 5 5852da35ad fix(auq): the interactive fence is quota-silent — it defers to the skill's own decision points
Burn-in calibration: run 1 (fence tail 'when unsure, ask') overshot the
plan-ceo review band at reviewCount=8; run 2 (tail mentioning 'HOW MANY
questions') undershot at 1. Any ask-count language in the fence anchors the
model in one direction or the other. The tail now says only: classify as
interactive, then follow the skill's own decision-point instructions exactly
as written. Pins updated to forbid count language in either direction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:58:35 +00:00
Denis ZjukowandClaude Opus 5 5b55be1f7e fix(open-gstack-browser): pre-flight cleanup never killed the stale daemon
Step 0 read the old pid with `grep -o '"pid":[0-9]*'` and Step 2 read the port
the same way. Neither can match. Every writer of that file in
browse/src/server.ts serializes with `JSON.stringify(state, null, 2)`, so the
bytes on disk are `"pid": 12060` — colon, space, digits.

The failure was silent in the worst way. `_OLD_PID` came back empty, the kill
never ran, browse.json was deleted anyway, and the next `connect` died with
"existing daemon has different config (proxy/headed mismatch)" — an error
pointing at proxy/headed flags rather than at the cleanup that no-opped. Caught
against a daemon left over from a reboot: the operator was told to check flags
they had never passed.

Both patterns now accept optional whitespace. The new tripwire does not match
strings — it RUNS the snippets the skill hands the agent, against a state file
written exactly the way the server writes one, and asserts pid and port come
back out. A third case pins the coupling to `JSON.stringify(state, null, 2)`,
so a switch to compact JSON surfaces as a failing expectation rather than as
silence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0111Mq3JGwZDcstn5wYcbhSw
2026-08-31 20:56:33 +00:00
Denis ZjukowandClaude Opus 5 8c7ff15fc4 fix(memory-helpers): a slow gitleaks probe no longer disables secret scanning
`gitleaksAvailable()` cached every failure the same way, so a 2s timeout on
`gitleaks version` was recorded as "the binary is absent" for the rest of the
process. One busy moment and the whole ingest ran unscanned behind a single
stderr line — a fail-open outcome decided by machine load rather than by
anything about the machine's setup. The caller only acts on
`scanner === "gitleaks"`, so every later file was written with no scan and no
second warning.

The probe now classifies three outcomes. ENOENT (and a present-but-unusable
binary: bad exit, EACCES) stays cached — that is a fact about the box, and
re-probing it per file would be waste. A timeout gets one retry on a 10s
budget, and if that also expires nothing is cached: the file is reported
unscanned, the warning says so in those words, and the next file probes again.

Observed under the 7-way sharded free-test runner, where spawning a shell
script inside a temp bin dir took longer than the 2s budget.

Tests: the retry path, the no-cache-on-timeout path (the second call must
re-probe), and the cached-absent path. The fake gitleaks hangs for 30s rather
than racing a short sleep against a short budget, and the budgets are chosen so
load cannot flip an outcome: 30s where the retry MUST answer, 800ms where the
probe MUST expire. An earlier draft used 1s/5s and flaked under the same shard
runner this commit is about. The existing probe test pinned `detect` to
calls[1], which a retry breaks; it now asserts the order instead of the index.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0111Mq3JGwZDcstn5wYcbhSw
2026-08-31 20:56:32 +00:00
Peter van LeeuwenandClaude Fable 5 5424ac5fe0 fix(gbrain-detect): classify gbrain >= 0.43 held-lock refusal as engine-locked
gbrain 0.43+ refuses a held PGLite lock with exit 1 and the message
"GBrain's local database is already open through `gbrain serve` (MCP,
PID N)" instead of the pre-0.43 exit 124 + "connect timed out" that
the #2194 branch matches. The message matches no known pattern, so the
classifier falls through to the defensive broken-config default — and
Step 1.5 of /setup-gbrain and /sync-gbrain then tell the user to move a
perfectly healthy config.json aside and re-init the engine.

Reproduced live on gbrain 0.43.0.0, 0.44.0.0 and 0.46.30.0: with a
serve holding the lock, gstack-gbrain-detect reports
gbrain_local_status=broken-config; after stopping the serve it reports
ok with the same untouched config.

Match on the stable substring "already open through", mirroring the
existing #2194 branch semantics: engine-locked for pglite, broken-db
otherwise. Adds a fake-gbrain behavior for the 0.43+ refusal plus two
cases (pglite -> engine-locked, postgres -> broken-db).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:56:32 +00:00
y$un_andCursor 3599a3d4df fix(config): reject malformed cross_project_learnings at set
A typo was stored with exit 0, so the feature stayed off and the first-run prompt never returned. Reject like codex_reviews; do not coerce.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 20:56:32 +00:00
schienbiz a97aff9d0b fix(redact): install-prepush-hook refreshes a stale managed hook
The marker check returned before the only writer, so once a repo had the
hook, no later change to the wrapper could ever reach it. The `printf x`
fail-open fix (v1.64.0.0) has still not landed in any repo that received
the hook before it, and a wrapper naming a gstack that has since moved
stays pointed at a dead path for the same reason.

Compare the body against what this version generates: rewrite on drift,
stay a no-op when identical. The chained pre-push.local is untouched on
both paths.

The existing trailing-newline regression test cannot catch this — it
installs into a repo with no prior managed hook, the one case that was
never broken.

Wave-amended: spawnSync timeouts added to the new tests (v1.77 sync-spawn tripwire)
2026-08-31 20:55:56 +00:00
schienbiz 49fa1a0b88 fix(redact): install-prepush-hook refreshes a stale managed hook
The marker check returned before the only writer, so once a repo had the
hook, no later change to the wrapper could ever reach it. The `printf x`
fail-open fix (v1.64.0.0) has still not landed in any repo that received
the hook before it, and a wrapper naming a gstack that has since moved
stays pointed at a dead path for the same reason.

Compare the body against what this version generates: rewrite on drift,
stay a no-op when identical. The chained pre-push.local is untouched on
both paths.

The existing trailing-newline regression test cannot catch this — it
installs into a repo with no prior managed hook, the one case that was
never broken.
2026-08-31 20:55:44 +00:00
alopes50andClaude Opus 5 0340862e01 fix: don't flag git SSH remotes as pii.email
`pii.email` matches the `git@github.com` inside
`git@github.com:acme/widgets.git`. That is a transport user@host, not a
person's address, so any diff touching a clone URL -- a deploy config's
repo URL, a submodule entry, a README clone line -- draws a spurious
MEDIUM from the pre-push hook.

Suppressed by URL shape rather than by adding `git` to
EMAIL_ALLOW_LOCALPARTS. A bare `git@` allowlist entry would also
suppress a genuine address at a domain that merely begins with "git"
(git@gitmail.com), converting a false positive into a false negative --
the worse failure for a guardrail. Two shapes are accepted:

  - `<user>@<host>:<path>.git` for ANY host, covering self-hosted
    remotes, plus the equivalent ssh:// URL form.
  - `git@<known-host>` for github.com, gitlab.com, bitbucket.org and
    ssh.dev.azure.com, whose bare form appears in docs and in
    `ssh -T git@github.com` connectivity checks with no path at all.
    Matched exactly, so gitmail.com is unaffected.

emailAllowed now receives the normalized text and the span offset so it
can see that surrounding shape; it had only ever been passed the matched
span.

Tests pin both directions: the SSH remotes go quiet, and a real address
still fires -- including at a git host (alex@github.com) and at a
git-prefixed domain (git@gitmail.com).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:52:29 +00:00
David Park cc94bc34ba fix(redact): stop reporting .env.local as an internal hostname
`internal.hostname` ends in `.local|.prod|.staging|…`, so `.env.local`
matches on `env.local` and a dotenv FILENAME is reported as a leaked
internal host.

The collision is not exotic. It fires on `--env-file=.env.local` in an npm
script, `.env.staging` in a README, `.env.prod` in a .gitignore — ordinary
lines on branches that leak nothing. Measured on one private repo, three of
four MEDIUM findings in a routine push were this, and the fourth was a
deleted localhost URL. That ratio is the real cost: a scanner that reports
package.json is one people learn to skim, and skimming is how the HIGH
finding it exists for gets missed.

The guard follows the `insideUuid` precedent and stays deliberately narrow —
it exempts only a span beginning `env.` immediately preceded by a dot, i.e.
the literal `.env.<suffix>` form. `api.corp.local`, `build-7.internal` and
`myenv.local` all still report.

The test pins both directions, and the negative controls are the point: an
exemption written as "any span ending .local" would pass the dotenv half
while quietly gutting the pattern for every real host. Verified red/green —
with the validate hook removed, exactly the 6 dotenv cases fail and all 9
real-host controls still pass.
2026-08-31 20:52:28 +00:00
schienbiz b77c1923c8 feat(redact): add Groq, Tavily and Notion API key patterns 2026-08-31 20:52:28 +00:00
Udhdhav kheni 4dc3516bcc fix(codex): a CLI that cannot execute no longer reports CODEX_MODE: ready
Follow-up to #2477. The model probe it added does a real round trip, but its
final branch is the `else` of a "model 400" grep, so it swallowed spawn ENOENT,
non-executable binaries and missing vendor payloads alongside genuine network
timeouts. All three are deterministic — retrying never helps — yet they landed
in the fail-open bucket and resolved to `ready`, so every Codex pass was
skipped in silence and the review reported itself complete.

Observed live: @openai/codex was on PATH with an empty
vendor/aarch64-apple-darwin/codex/ directory. gstack said `ready` for two
months while no Codex pass ran.

Three changes:

- `_gstack_codex_model_probe` classifies deterministic install failures (exit
  126/127, or stderr matching ENOENT/ENOEXEC/EACCES/"cannot execute binary
  file") as MODEL_UNUSABLE_INSTALL, exit 2, never cached — a reinstall is
  picked up on the next probe. Exit 124 and genuine transients still fail open,
  which is what #2477 intended.

- The preflight chain captures the probe's code instead of testing it for
  truthiness, so exit 2 routes to a new `broken_install` mode whose remedy is
  `npm install -g @openai/codex` rather than "check your model pin". A missing
  binary and an unusable model are different problems with different fixes.

- `_gstack_codex_version_check` no longer reads a broken CLI as healthy. It ran
  `codex --version 2>/dev/null | head -1`, which captures head's status, not
  codex's — and 2>/dev/null discarded the one diagnostic available. It now
  captures the real exit code and warns on non-zero. Empty-but-successful
  output stays silent, per the existing "empty output → OK" case.

Tests: 6 added to test/codex-hardening.test.ts covering both broken-install
shapes, the exit-2 contract, no caching, the transient still failing open, the
model 400 still classifying as MODEL_UNUSABLE, and the version-check warning.
845 pass / 0 fail across all 8 suites touching the changed files.

Closes #2742

Wave-amended: autoplan hand-maintained preflight chain completed (tmpl+render); install-signature grep gated on failed spawn only; goldens regenerated against the wave tree (author's golden commit 5797d326 superseded); +2 tests
2026-08-31 20:52:20 +00:00
LockyerandCursor 567f06217a fix: point ship design-checklist at installed gstack/review path
The /ship Design Review step skipped the checklist because the generated path omitted the gstack/ install segment. Sync the generated skill doc and pin a regression assertion.

Co-authored-by: Cursor <cursoragent@cursor.com>
Wave-amended: goldens regenerated against the wave tree (author's golden commit 8e7a03ca superseded)
2026-08-31 20:50:27 +00:00
y$un_andCursor 31f4d1b387 test(gbrain-sync): blank inherited GBRAIN_HOME in the pin child
Lock paths are checked before pgrep. Spreading process.env let a runner
GBRAIN_HOME with a live lock refuse the case before the stub ran.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 20:50:08 +00:00
y$un_andCursor 924744fed9 test(gbrain-sync): stub pgrep so the pin case is hermetic
The only non-dry-run --code-only child hits #1734's PATH-resolved
autopilot probe. A live host daemon is a correct refuse; the test
cannot inject processRunning. Neutralize pgrep in the fixture bindir
instead of adding a production env hatch.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 20:50:08 +00:00
Garry TanandClaude Fable 5 ddcd4073ef fix(deps): dependency wave — 105 OSV advisories → 3 reasoned suppressions, all lanes verified on the pinned scanner
Root: overrides pin ip-address 10.3.1 (defeats BOTH nested nodes — socks'
range pull and express-rate-limit's exact 10.1.0 pin, which a top-level bump
provably cannot reach) and sharp 0.35.0 (GHSA-f88m, HIGH; transformers still
pins ^0.34 upstream — smoke-tested round-trip); marked ^18.0.11; full in-range
lockfile refresh clears hono, fast-uri, protobufjs, qs, body-parser, nanoid,
uuid, immutable and friends.

lib/diagram-render (via its own build-script contract: exact pins edited,
fresh lock, dist rebuilt): mermaid 11.16.1, @excalidraw/excalidraw 0.18.1,
@excalidraw/mermaid-to-excalidraw 1.1.2 → 2.2.2 — the 1.x line exact-pinned
mermaid 10.9.x and dragged the entire duplicate mermaid-10 advisory chain
(dompurify 3.1.6, nanoid 3.3.3, lodash-es); the bundle shrinks 9.96 → 7.59 MB
with the duplicate mermaid gone. Nested exact pins that survived get scoped
overrides (nanoid 5.1.16, lodash-es 4.18.1).

Verification: clean-worktree frozen-lockfile installs (root + nested) + the
SAME osv-scanner release the action pins (v2.3.8) with the workflow's exact
scan-args → exit 0, 'No issues found'. Smoke tests cover the override
surfaces (sharp round-trip, ip-address lockfile assertion, marked parse);
socks + diagram-drift suites already pin the rest.

Supersedes #2695 (its own lockfile kept socks/ip-address@10.2.0; @anupamme's
report credited for the parallel diagnosis).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:49:20 +00:00
Garry TanandClaude Fable 5 716196bba5 fix(ci): OSV suppression config actually loads — explicit global --config + expiring, reasoned ignores
The ignore file was inert from v1.65.0.0: OSV-Scanner only auto-discovers
configs named osv-scanner.toml (no leading dot) and applies them
per-directory, so the root config never covered lib/diagram-render/bun.lock
either way. The workflow now passes --config=.osv-scanner.toml globally.
Every IgnoredVulns entry carries a reason with an upgrade trigger and an
ignoreUntil expiry (~90 days) so suppressions must be re-justified. A wiring
test pins flag ↔ filename ↔ entry hygiene so the file can never silently go
inert again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:49:05 +00:00
Garry TanandClaude Fable 5 6258257cfc fix(auq): the interactive fence classifies the session — it never nudges ask-count
Burn-in run 1 of the periodic repro overshot the review band (reviewCount=8 >
CEILING=7) with the fence's 'when unsure, ask' tail: that phrasing is a quota
nudge, not a classification default. The fence now states it only classifies
the session and never changes how many questions the skill asks. Pin added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:48:56 +00:00
Garry TanandClaude Fable 5 04d61024d7 fix: mktemp failure aborts loudly at all three skill-content sites; failed upgrade swap restores the backup (#2679)
An empty $(mktemp) result silently disabled the redaction pass (redact-doc
resolver, ship pr-body) and made /gstack-upgrade's vendored path destructive:
clone lands at "/gstack", the swap mv fails, and rm -rf then deletes BOTH the
live install's backup and "". All three sites now guard the assignment with a
loud exit; the vendored block additionally restores the backup when the swap
fails (same failure class — backup deletion after a failed mv) and the GitLab
MR path sends the SCANNED file's bytes instead of re-rendering an unscanned
heredoc. bin/gstack-redact rejects an explicit empty --from-file path instead
of silently falling through to stdin.

Receipts: 6 of 8 new regression checks fail on a v1.77.0.0 scratch worktree.

Fixes #2679

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:47:41 +00:00
Garry TanandClaude Fable 5 95d3aa17e9 fix(auq): spawned trigger is objective — explicit declaration or STATUS echo, never inference (periodic-lane AUQ collapse)
The v1.76 spawned rule's parenthetical '(or your dispatch prompt marks this
session as spawned)' let the model INFER spawned status from a scripted-looking
prompt in a CI-looking session and silently auto-choose every review-phase
question: reviewCount=0 across the plan-review periodic E2Es (weekly run
33363624506, 9 of 14 failed shards; reproduced locally, zero AUQ fingerprints).
Env and hook paths were excluded by inspection: hermetic children echo
SESSION_KIND: interactive (CLAUDE_CODE_ENTRYPOINT=cli beats CI markers) and the
question-preference hook isn't installed there.

The trigger is now objective: the echoed SESSION_KIND: spawned STATUS line, or
an EXPLICIT dispatch-prompt declaration ("you are a SPAWNED subagent") —
declared, never inferred — with an absence-safe interactive fence: CI env vars,
scripted-looking or pasted prompts, and write-to-this-exact-file instructions
are NOT spawned markers. The prose channel stays because Task-tool subagents
inherit the parent env (no spawned prefix) — their dispatch prompt is the only
signal; #2733's env-prefix channel is untouched.

19 carve skeleton ceilings re-pinned with measured values (+~440 bytes/skill);
ship goldens refreshed for all three hosts; resolver pins extended with the
no-inference regression tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:36:12 +00:00
Garry TanandClaude Fable 5 e76f65a8da v1.77.0.0 feat: test-infrastructure overhaul wave 1 — matrix deletion, flake telemetry, sync-spawn wedge class extinct (#2746)
* fix: pin the claude CLI to an exact version in the CI image + tripwire

The image installed @anthropic-ai/claude-code UNPINNED and rebuilt weekly
'to pick up CLI updates' — while bun sat carefully pinned at 1.3.13 two RUN
lines above. The PTY harness screen-scrapes this CLI's TUI, and that drift
broke it three separate times (welcome-screen wedge on 2.1.233, skillify
HOME discovery on 2.1.237, guard/freeze hooks on 2.1.162), each debugged as
a flake first. Pin 2.1.251 (current latest), bump deliberately via a PR
that runs the PTY gate, and enforce with test/ci-image-cli-pin.test.ts:
any global npm install in Dockerfile.ci without an exact @X.Y.Z pin fails
the free suite. The weekly ci-image cron stays as a cheap tag self-heal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: stamp the claude CLI version into every eval-store run record

Three harness breakages were traced to claude-CLI TUI drift only after long
flake hunts, because no run record said which CLI it actually exercised.
EvalCollector now stamps claude_cli_version (claude --version, cached once
per process, 'unknown' when the binary is absent) into both partial and
finalized records — schema-additive optional field, no SCHEMA_VERSION bump.
Correlating a flake wave with a CLI release becomes a grep over
~/.gstack/projects/<slug>/evals/ instead of archaeology.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: give the spinning-shard kill test load headroom (30s -> 90s)

The test spawns and group-kills three real children (one a busy-loop
burning a full core) while five sibling shard processes compete for eight
vCPUs. Under full-suite load it blew bun's default 30s per-test ceiling at
30,009ms — while passing in isolation in 1.4s — and red the only required
lane. Every assertion in it is event-based (statuses, group-kill proof,
heartbeat lines); the sole latency claim is the <30s kill-deadline sanity
bound, which stays. Explicit 90s headroom, not a weakened oracle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: green-by-skip census — skip counts in the classifier, all-skipped labeling in the paid runner

bun's 'Ran N tests' line COUNTS skipped tests, so a codex/gemini shard
whose every test self-skipped (binary absent on the runner — true of every
CI runner today) exits 0, dodges the hollow-shard guard, and reads as
coverage in the weekly census. The classifier now parses bun's ' N skip' /
' N pass' recap lines; ShardOutcome carries skippedTests; formatSummary and
the fail-closed slices report label an all-skipped pass explicitly:
'all N tests SKIPPED — verified nothing'. Status stays 'passed' (external
service availability is host state, not a repo regression) but the census
can no longer mistake absence for coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: extract composite actions for eval-lane setup; surviving lanes gain the fail-fast registry verification

'Fix bun temp' x3, 'Restore deps' x5, 'Seed claude interactive config' x3,
and 'Register gstack skills' x3 were byte-near-identical copies across the
legacy matrix, the sliced lane, and the periodic lane — and only the MATRIX
copy of register-skills carried the 19-line dangling-symlink + frontmatter
fail-fast loop written after a silent 'Unknown command' + 35-min-timeout
incident. Extract all four into .github/actions/ composites; the register
composite carries the verification loop (generalized over the skill list),
so the sliced and periodic lanes — the lanes that SURVIVE the matrix
deletion — now inherit the check they had silently dropped. Matrix-job
inline copies are left untouched: that job is deleted next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: delete the legacy 17-row eval matrix — the sliced lane is the only paid lane

Every PR paid twice: the hand-enumerated matrix (18 test files, 22.6 min,
~$21 API measured on run 33263204465) ran serialized AHEAD of the strictly
superior sliced lane via 'needs: evals' — 35.5 min wall and ~2x paid spend
for the same diff. 14 of 17 rows carried no tier:, so periodic Opus
benchmarks leaked into every PR (the e2e-plan row alone: 12/12 tests,
21.7 min, $7.28 — the wall-clock bound of ALL of CI).

Parity receipt (static, pre-deletion): the sliced lane's gate census (49
files, derived from the runner itself) strictly contains all 18 matrix test
files, plus 31 files the matrix never ran. Pure deletion — one revert
restores it. The PR comment moved into slices-report (same '## E2E Evals'
upsert marker, now sourced from slice artifacts + carrying the fail-closed
reconciliation verdict). plan-slices loses the needs edge; the dead
workflow-level EVALS_TIER env goes with it.

test/evals-workflow-matrix.test.ts (and its KNOWN_MATRIX_GAPS /
KNOWN_TIER_UNSET burn-down ratchets — retired: the sliced census makes
'every gate file runs' true by construction) is rewritten as
test/evals-workflow-wiring.test.ts: matrix stays deleted, planner/executor/
report tier + slice-count agreement, both surviving lanes on the shared
register-skills composite with its fail-fast verification loop, PR comment
survival. Expected: PR eval wall 35.5 -> ~13 min, per-PR paid spend ~halved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: provider-runner timeouts kill the whole process GROUP; codex/gemini inherit the orphan-drain hardening

All three provider runners (claude/codex/gemini) killed only the direct
child on timeout: tool subprocesses the CLI spawned survived as orphans
holding our pipes open and burning shared API rate (observed: a 600s
timeout stretching past 1400s; a stalled run once burned a core for 15
hours). gstack-detach's watchdog had the same shape one level up — killpg
SIGTERM, 5s grace, then a direct-child proc.kill() that orphaned
grandchildren.

Fix: spawn provider children via node:child_process with detached (own
process group) and killProcessGroup(SIGKILL) in the timeout handler —
runShardChild's proven pattern, EPERM/ESRCH fallbacks included. The codex
and gemini copies also gain the reader.cancel() + stderr Promise.race
hardening only the claude copy had (they still carried the blocked-drain
hang it fixed). gstack-detach's watchdog now group-SIGKILLs after the
grace.

Regression net: test/session-runner-groupkill.test.ts drives the REAL
runSkillTest against a fake claude shim (PATH override) that spawns a
grandchild and wedges — the run must classify timeout within budget and
leave neither shim nor grandchild alive — plus source pins on all three
runners (detached + killProcessGroup, no bare timeout kill, no Bun.spawn
reversion).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: skill-e2e-opus-47 renders SKILL.md fixtures into a mkdtemp — never the live tree

mkEvalRoot ran gen-skill-docs with cwd=ROOT, regenerating every in-repo
SKILL.md mid-run while concurrent paid shards copyFileSync those same files
in their beforeAll (EVALS_JOBS>=4 locally, 2 per CI slice) — a sibling
could capture a half-regenerated or opus-rendered SKILL.md, and a timeout
before afterAll stranded the whole tree at the wrong model for every later
shard. A cross-shard race that could flake ANY concurrent paid test.

Render via the --out-dir flag gen-skill-docs grew for exactly this reason
(mirrors the repo layout, which is all the fixture reads), read the skill
heads from the render dir, delete it, and drop the afterAll restore-regen
entirely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: claude CLI version resolves in the runner parent, never on a test thread

Eng-review finding: getClaudeCliVersion's fallback is a SYNCHRONOUS
spawnSync on the same thread that polls concurrent PTY/session tests — the
judgePtyState blocking class this overhaul kills elsewhere. The paid runner
parent now resolves it once (cached) and stamps GSTACK_CLAUDE_CLI_VERSION
into every shard's env; eval-store short-circuits on the env var, and the
fallback spawn's budget tightens 10s -> 3s (bounded one-time stall, records
'unknown' on a slow CLI).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: wire skippedTests end-to-end through runPaidShard

The census unit tests hand-built outcomes and the classifier tests parsed
strings; nothing proved a real child's ' N skip' recap flows into
outcome.skippedTests and the formatSummary label. A commandFor fake now
prints the recap shape and the test asserts the parsed counts, the
all-skipped predicate, and the 'verified nothing' label.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: make the setup composites rerun-safe (codex diff-review hardenings)

restore-deps: 'cp -r SRC node_modules' with an existing node_modules NESTS
the copy and leaves stale deps active — rm first. register-gstack-skills:
'ln -snf' hard-errors under set -eu when a REAL directory occupies the
gstack slot — clear a non-symlink leftover first. CI workspaces are fresh
today; a reusable composite must survive dirty reruns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: sweep — every sync spawn in the test trees carries a timeout (436 sites, 157 files)

spawnSync/execSync/Bun.spawnSync BLOCK the main thread, so bun's in-process
per-test timeout can never fire while one waits — a hung child (stdin read,
network probe, dead daemon) wedges the whole shard until the runner's
external wall-clock SIGKILL. This exact class reached main: free-tests run
33262077256, test/gstack-memory-ingest.test.ts (normally 2.3s) held shard 2
at the 360s wall while its five siblings finished in ~65s.

Mechanical sweep in two waves (12 + 4 fan-out agents, every edit verified
against its call site): default timeout: 30_000 (matches the free runner's
per-test budget), 120_000 for genuinely slow ops (installs, builds,
playwright, provider CLIs), helper wrappers fixed ONCE where call sites
route through them. Sites that only LOOK like calls (string fixtures, grep
needles, comments) were skipped with reasons — the enforcement commit that
follows marks them exempt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: sync-spawn timeout tripwire — the wedge class stays extinct

Free scanner over all test trees (test/, browse/test/, design/test/,
make-pdf/test/, ios-qa, browser-skills): every spawnSync/execSync/
Bun.spawnSync call site must carry a timeout within a 30-line options
window, or an explicit '// tripwire-exempt: <reason>' marker. Comment
lines are skipped; exemptions are counted and ratcheted shrink-only
(ceiling 6 = the 6 string-fixture/grep-needle sites where the pattern is
CONTENT, not a call — marked in this commit). A scan-sanity test pins that
the scanner still sees >100 real call sites so it can never rot to a
vacuous green. Companion to the 436-site sweep in the previous commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: paid-lane flake telemetry — record-level attempts, flaky_retries, report surfacing

bun --retry leaves a retried pass INVISIBLE in its output: a fail-then-pass
prints the error detail but no (fail) result line and recaps as a clean
pass (probed live on 1.3.10). So attempts are recorded where they cannot
lie: EvalCollector.addTest stamps a 1-based attempt on same-name re-records
(a retried test runs its body again and re-records), finalized runs carry
flaky_retries, printSummary warns loudly, and the fail-closed slices report
lists every passed-only-on-retry test — recorded and ranked, never blocking
and never silent. Cross-model confirmed (codex reached the same don't-parse
-the-stream conclusion independently).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: free-lane flake ledger — retry ON in CI, flaky-passes recorded and uploaded

The runner's attribution-gated flaky-retry pass (cap 5, truncation veto)
was OFF in the required lane and its FLAKY-PASS evidence was console-only —
so a single timing flake red the merge gate while repeat offenders stayed
unenumerable. free-tests.yml now sets GSTACK_FREE_RETRY_FLAKY=1 and points
GSTACK_FLAKE_LEDGER at runner.temp; every flaky-pass appends a JSONL entry
(SINGLE writer: the parent runner — no concurrent-append hazard by
construction; fail-open with a loud warning so a broken ledger can never
red the lane) and the artifact uploads UNCONDITIONALLY — a flaky-pass run
is green, which is exactly when the evidence matters. Wiring pinned by
free-tests-workflow-wiring; ledger behavior unit-tested incl. the fail-open
path. Matches 2026 industry practice (retry for data, quarantine out of
merge-blocking but never out of logging) with the repo's own receipts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: eval:flake-rank — the flake-telemetry dial

Aggregates per-test series across every finalized eval-store run (shard
dirs included) plus the free flake ledger: runs, fails, RETRIED PASSES
(the flake signature), avg duration — ranked retries-first. This is the
readable dial behind two policies: a flaky pass never blocks a merge but
is always ranked here, and the WS16 required-check promotion needs weeks
of clean flake-rank, not vibes. --json for machines, --dir for downloaded
CI artifacts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: two-phase session timeout — silent APIs die at the startup grace, named

The single spawn-armed timer charged API queue latency to the work budget:
the recurring '0 turns / $0.00 / x3 attempts' failure with four budget-bump
receipts (180->300s, 240->360s, 300->420s, 90->300s). Split: startup phase
(no NDJSON byte yet) kills EARLY at min(grace, timeout) with the distinct
exitReason 'timeout_startup' — an availability verdict, not transcript
archaeology — and the work phase arms on the first byte for the REMAINING
budget, so total wall never exceeds the timeout (tier envelopes are
margin-free: tests pass timeout: CAPTURE_MS and bun-budget the same tier).
Local grace 90s (observed queue latency 60-90s), CI floor 300s (TODOS-filed;
shared runners queue harder), both pinned by the new grace tests with fake
-claude shims covering the late-first-byte and silent-API paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: census integrity — 17 phantom selection keys deleted, reverse invariant added, gitignored dep patterns replaced, local map forks derived

The merge-blocking gate census counted tests that could not run. Deleted
(critic-verified against both quoted-occurrence and dep-registration
liveness): 7 *-prosons-format keys with no declaring test, ship-plan-
completion/-verification, review-plan-completion, design-shotgun-path/
session/full, autoplan-core (dead ~10 months), e2e-harness-audit (its
namesake is a FREE-suite file), plus 2 dead LLM-judge keys and 2 free-file
keys (budget-regression-pty, global-discover) misplaced in the PAID maps.
Census: 191 -> 174 keys, gate 86 -> 78 honest.

The new reverse invariant in touchfiles.test.ts makes the class structurally
impossible: every key must be quoted in a living paid test file OR
registered to an existing paid test file via its dep list (the constructed-
name binding the 2026-08 self-registration sweep established) — zero
exceptions needed today, with a live-file check on any future exception.

Also: '.agents/skills/**' dep patterns replaced with the generator
(scripts/gen-skill-docs.ts) — .agents/ is gitignored, so those patterns
could NEVER match a git diff and review-template edits silently stopped
selecting codex/gemini tests; the codex/gemini local touchfile maps are now
DERIVED from the canonical map (loud throw if a key vanishes) instead of
hand-forked copies that had already drifted. ios-qa-e2e demoted gate ->
periodic: its gate declaration was never executable in CI (hardware
exclusion only applies at tier=periodic), so every Linux PR planned a
hollow shard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: routing journeys lose their answer key and end at the routing decision

The journey tests exist to catch skill-DESCRIPTION regressions (touchfiles:
*/SKILL.md.tmpl), but the fixture CLAUDE.md shipped an explicit
prompt->skill lookup table — with the answer key in context, a badly
regressed frontmatter description still routed correctly, so the tests
could not fail on the exact class they select for. The fixture now carries
only the generic invoke-skills nudge; the frontmatter carries the routing
load. Also capped all 10 journeys at maxTurns 2 / tools [Skill, Read]:
only the FIRST Skill call is asserted, so 5 turns of Read/Bash/Glob/Grep
was pure spend — roughly halves each journey's cost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: retire decided A/B experiments; vendor the pre-cut fixture; ban raw-SHA fixtures

Three one-shot decision experiments kept re-running weekly as N=1
stochastic comparisons — flaky by construction with near-zero remaining
information: skill-e2e-auq-repetition-cut-ab (its own header: gate "passed
pre-landing, approved 2026-08-25"), skill-e2e-preamble-script-ab ("demoted
post-Phase-3"), and opus-47's fanout arm-vs-arm (parA >= parB across two
SINGLE stochastic runs — a coin flip). Deleted, with their selection keys;
the SDK overlay-harness stays as the maintained instrument for the next
experiment, and opus-47 keeps its routing-precision cases.

verboseSkill() now reads the VENDORED test/fixtures/auq-pre-cut-...-SKILL.md
instead of `git show ab66193e^:...` — a branch-local ref that dies on
branch prune and already failed on shallow clones. New free tripwire
(test/git-ref-fixture-tripwire.test.ts) bans the raw-SHA fixture class
outright: quoted SHA:path rev-specs and gitRef-style hex defaults in the
test trees fail the suite with the vendor-instead instruction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: demote plan-ceo-review-expansion-energy to periodic

Opus generator + a subjective 2-axis >=4/5 LLM-judge threshold sat in the
MERGE-BLOCKING gate — the exact class its sibling posture tests were
demoted for, with a receipt (a +21-line preamble change once flipped the
score). CLAUDE.md's own tiering rule: Opus model test -> periodic. The
weekly lane keeps the regression signal; merges stop paying a judge-
temperament tax.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: paid shards get per-shard TMPDIR + CHROMIUM_PROFILE isolation and a kill-path cleanup backstop

The free runner treats this isolation as MANDATORY (two concurrent shards
on one Chromium profile kill each other's browser; shared tmp
cross-contaminates) — the paid lane had none of it. Doubly load-bearing
here: a shard that hits its 30-min wall is group-SIGKILLed, so per-test
afterAll cleanup never runs; the rmSync backstop is the only thing keeping
wedged runs from accumulating full git-repo workspaces in the shared
tmpdir forever. This is the DAG prerequisite for raising EVALS_JOBS (next
commit) — more concurrency on shared state amplifies exactly the
shared-tree race class opus-47 exhibited.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: paid-runner defaults 4x4 -> 8x2 — halve the local gate worst case

39 of 75 skill-e2e files hold exactly ONE test, so within-shard
concurrency was dead weight for most shards: 4 jobs x 4 concurrency
yielded only ~4-6 real in-flight sessions and a 13-wave local gate worst
case (~6.5h). 8 jobs x 2 gives ~10-13 in-flight — under the
documented-safe ~15 — and ~7 waves (~3.3h worst case). CI lanes keep
their explicit EVALS_JOBS env (2 per slice; 4 for gate-census); this
changes local defaults. Rollback trigger: sustained 429 storms in the WS1
telemetry across 2 PR cycles. test/eval-detach-timeout-floor.test.ts
recomputed green (the raise LOWERS the worst-case floor).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: SHA-pin every action in the secrets-bearing eval lanes

evals.yml and evals-periodic.yml execute PR-authored code with three
provider API keys in env, yet rode mutable action tags (@v7/@v8/@v2/@v4)
— while quality-gate.yml, osv-scanner.yml, and dependency-review.yml
already model the SHA-pin pattern. All 30 uses sites across both lanes now
pin the exact commit (tag noted in a trailing comment); dependabot's
github-actions ecosystem keeps them fresh via PRs instead of silent tag
moves. Pulled forward from the plan's endgame on the CEO-review + outside-
voice agreement: supply-chain pins on secret lanes go first, not last.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: sweep wave 3 — the execFileSync family gets timeouts (90 sites, 17 files)

The tripwire's regex covered spawnSync/execSync/Bun.spawnSync but not
execFileSync — an entire blocking sync-spawn API family that could
reintroduce the shard-wedge class undetected (ship review army). Same
mechanical recipe as waves 1-2: timeout: 30_000 default, 120_000 for slow
ops, shared wrappers fixed once, string-needle sites skipped with reasons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: review-army + adversarial test hardening

- Tripwire scans execFileSync too (ceiling 8: two more grep-needle string
  exemptions); merge-introduced timeout-less spawnSync in
  question-preference-hook fixed — the tripwire caught a site that landed
  on main AFTER the sweep, on its first day.
- gstack-detach gains TWO watchdog kill regression tests: TERM-immune
  grandchild (the killpg-after-grace escalation) and the leader-dies
  variant (the pgid-at-spawn fix — the case the first test cannot see).
- eval-flake-rank gets its unit suite (final-attempt accounting, artifact
  exclusion, shard recursion, recency bound).
- Groupkill/startup-grace shim markers are per-run unique (pid-suffixed
  sleep durations): sibling Conductor worktrees run free suites with no
  machine lock, and fixed markers let one run pgrep/pkill the other's
  shims — a cross-run flake inside the anti-flake tests.
- flake-ledger test pins the project-scoped local default; stale empty
  section headers in touchfiles-data deleted (they invited entries under
  deliberately retired categories).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: adversarial-review runtime fixes across the telemetry + kill paths

- session-runner: exit-labeling keys off 'exit', not 'close' — an orphan
  holding the pipes could relabel a REAL exit (auth failure) as
  'timeout_startup' availability noise; the kill path still always
  group-kills and cancels the reader (labeling and unblocking are separate
  concerns). Work phase arms on a flag, not firstResponseMs===0 (a same-ms
  first byte left the startup timer live all run). The CI startup grace is
  now a real FLOOR (Math.max), matching its name and pinning test.
- gstack-detach: pgid captured AT SPAWN (== child pid under
  start_new_session) — resolving it after the grace raised ESRCH once the
  leader died on SIGTERM, orphaning TERM-immune grandchildren forever.
- test-free-shards: ledger entries carry branch + git_sha (rev-parse split:
  '--abbrev-ref HEAD HEAD' printed the branch twice and recorded it as the
  sha); local ledger default is per-PROJECT, not the machine-global tmpdir.
- eval-flake-rank: per-LINE ledger parse (one torn JSONL line vanished the
  whole series), 60-day recency bound (transcript-bearing files are MBs),
  shared isFinalizedEvalResultFile predicate (the artifact-taxonomy rule
  lived in three places); eval-store exports the predicate and finalize
  stops computing flakyRetries twice; paid-shards cleanup uses async rm
  (a SIGKILLed shard's git-workspace teardown blocked every sibling's
  stream classification on the parent event loop).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: CI trust-boundary + fail-closed repairs (adversarial findings)

- Token/exec separation restored: slices-report (runs PR-authored code:
  bun install + the reconcile runner) drops to contents:read; the PR
  comment moves to a NEW slices-comment job holding the write token with
  ZERO repo code — no checkout, no bun, only downloaded artifacts + jq/gh.
  $GITHUB_ENV/BASH_ENV persistence is job-scoped, so the split is the
  boundary. The matrix-era report job had this property; the consolidation
  had regressed it. Pinned by the wiring test.
- Reconcile exit captured via PIPESTATUS[0] in BOTH lanes: GitHub's default
  run-step shell has no pipefail, so `$?` after `| tee` was tee's exit —
  the fail-closed gate was silently fail-open. Wiring test pins it.
- PR comment: final-attempt accounting restored the dropped COST
  accumulation (the dial read $0 forever), flaky passes render as the
  warning they are (never as failures), and a malformed tests[] artifact
  skips that file instead of aborting the whole comment under bash -e.
- Remaining mutable action tags pinned (free-tests upload-artifact,
  ci-image checkout/docker trio — the image publisher holds packages:write
  and feeds the secret-bearing lanes). restore-deps fallback installs
  --frozen-lockfile; register-gstack-skills validates skill names before
  its rm -rf.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump version and changelog (v1.77.0.0)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: update project documentation for v1.77.0.0

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: cross-model doc-review fixes — flake-ledger env knobs, CI retry-on note, stale version comment

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: correct CHANGELOG receipt numbers to measured values

Gate census keys: 78 -> 77 (bun-imported E2E_TIERS count). Sweep receipt:
586 sites/176 files -> 499 sites/146 files, measured by running this
branch's spawnsync-timeout-tripwire against origin/main (exit 1, 499
violations across 146 unique files; green on this branch).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: slices-comment creates the PR comment via REST — the write-token job has no git context

The token/exec split gives slices-comment NO checkout by design, and gh's
pr-comment subcommand resolves the repo FROM git — it died with 'not a git
repository' on PR #2746's first run (the update-existing PATCH path was
already explicit-repo REST and worked). Create now posts through
gh api repos/.../issues/N/comments, and the wiring test pins that no
git-context-requiring comment call can creep back into the job.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: startup-grace probes clear CI for local semantics; new probe pins the floor clamp

The two shim probes pass explicit 2s/4s graces, but in CI the runner clamps
any explicit grace up to the 300s floor (deliberate adversarial-review fix),
so 'silent API killed at the grace' died at the 30s work cap instead of 2s —
a deterministic red on every CI run, green locally. The probes now pin LOCAL
semantics with CI cleared (same save/restore pattern as their PATH shim),
and a fourth probe pins the clamp itself: CI=1 + 2s grace + 6s timeout must
kill at the 6s cap, still in the startup phase — proof an explicit low grace
cannot bypass the floor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 08:55:30 -07:00
Garry TanandClaude Fable 5 253d1dfe26 v1.76.0.0 fix: ship doc-sync survives Conductor — spawned subagent sessions reachable (#2733) (#2741)
* feat(session-kind): explicit GSTACK_SESSION_KIND override; skill-start spawned gates keyed on kind (#2733)

Claude Code subagents inherit the parent env byte-for-byte, so ambient
markers classify them as the parent's kind and the spawned classification
was unreachable outside OpenClaw. GSTACK_SESSION_KIND=spawned (step 0,
spawned-only by design) lets a dispatching skill mark its subagent per
command. skill-start now keys SPAWNED_SESSION and the spawned-session
instruction block on the resolved kind (was raw OPENCLAW_SESSION),
suppresses CONDUCTOR_SESSION for spawned sessions, gates all 11
interactive-onboarding blocks plus their ack-at-emit marker writes on
kind != spawned, and adds a destructive-gate carve-out to the spawned
block (conservative-continue, never prose-STOP).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(hooks): spawned-session escape in Conductor AUQ deny; override coverage in AUQ-error fallback (#2733)

Hooks inherit the harness env, so a per-command GSTACK_SESSION_KIND
prefix inside a subagent's bash can never reach them. Levers added:
a deterministic [conductor][spawned] auto-choose deny for env-level
spawned sessions (OPENCLAW_SESSION or session-wide GSTACK_SESSION_KIND),
and a spawned escape sentence appended to both hooks' prose directives
so a marked subagent that slips and calls AUQ resolves to auto-choose
instead of prose-STOP. The sentence lives in one shared constant
(hosts/claude/hooks/spawned-directive.ts) so the two paths can never
drift; destructive semantics are unified to conservative-continue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ship): Step 18 marks the document-release subagent spawned — env prefix + auto-choose prompt (#2733)

The dispatch prompt now (1) frames the run as a SPAWNED subagent whose
LAST line is machine-parsed, (2) instructs prefixing the preamble's
gstack-skill-start invocation with GSTACK_SESSION_KIND=spawned on the
same command line (template bash blocks don't share exports), and
(3) resolves every AUQ gate to auto-choosing the recommended option,
conservative on no-recommendation, never destructive. The JSON contract
gains a required "decisions" array (auto-chosen gates, printed to the
ship console — never embedded in the public PR body) and a placement
clause so the skill's own doc-health summary stops competing with the
LAST-line JSON. Tripwire pins added; codex/factory goldens refreshed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(auq-format): proactive SESSION_KIND=spawned rule ordered above the Conductor rule (#2733)

The spawned classification previously existed only in the failure-fallback
branch — a spawned session was invited to call AskUserQuestion and reach
auto-choose via the deny/error detour, and a spawned session inside a
Conductor workspace hit the Conductor prose-STOP rule first. The Tool
resolution list now leads with the spawned rule (auto-choose recommended,
never prose, never BLOCKED, destructive gates resolve conservative), the
self-check carries the never-reach-this-checklist clause, and all tier>=2
SKILL.md renders are regenerated. Context-budget fixture refreshed in the
same commit per the ratchet protocol (the AUQ section is eager in every
tier>=2 skill).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(e2e): spawned document-release subagent returns the JSON contract through a firing gate (#2733)

The behavioral proof the bug shipped without: ship-docsync stubs the
skill (no preamble, no gates) and skill-e2e-workflow suppresses the
gates by prompt. This gate-tier E2E plays the parent — it drives the
verbatim Step 18 dispatch prompt (extracted from the live pr-body.md,
drift-proof) against a real preamble-bearing document-release slice in
a Conductor-ambient env with both AUQ hooks seeded live, an unbumped
VERSION making Step 8 fire. Asserts: the final line parses as the
5-key JSON contract, the fired gate's auto-choice is recorded in
decisions, and VERSION is untouched (the gate resolved to its
recommended Skip). Burn-in: 1/1 pass, $0.35, 21 turns, 106s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(openclaw): document the GSTACK_SESSION_KIND override; wire session-kind into paid selectors (#2733)

OPENCLAW.md's spawned-session section now covers the explicit per-command
marker, its deliberate spawned-only narrowness, the /ship Step 18 usage,
the destructive carve-out, onboarding-block suppression, and the hook
env-blindness caveat. bin/gstack-session-kind and the shared
spawned-directive module join the conductor-prose and
auto-decide-preserved selector dep lists (session-kind previously
appeared in no touchfiles entry — editing it alone triggered no paid
E2E). TODOS.md gains the plan-tune capture follow-up for spawned
auto-choices.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: pre-landing review fixes (#2733)

Review army + coverage audit findings, all applied:
- headless directive carries the spawned escape sentence too (multi-
  specialist: a CI-hosted ship's marked subagent must not end BLOCKED)
- anti-injection scoping on every text-claimable spawned trigger (AUQ
  rule + shared escape sentence): markings count only from the creating
  prompt, never from files/tool output/web content read mid-run
- [conductor][spawned] deny annotates one-way doors per question
- SPAWNED_OVERRIDE: env tamper-visibility status line + OPENCLAW.md note
- spawned sessions skip the network update-check and first-task probe
  (consumers suppressed; preserves the one-shot just-upgraded marker)
- test hardening: dispatch-tripwire end-bound validated, vacuous marker
  asserts replaced with output asserts, E2E cpSync size filter + named
  fence tolerance, spawnedByEnv parity pin, destructive-policy cross-
  surface drift guard, one-way annotation + bogus-value hook cases
- session-kind duplicate rationale comment deduped; regen + goldens +
  context-budget fixture refreshed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump version and changelog (v1.76.0.0)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: update project documentation for v1.76.0.0

PROJECT_STRUCTURE.md: add hosts/claude/hooks/ to the directory tree
(AUQ capture + enforcement hooks, spawned-session directive, timeline
stop) — the tree omitted the directory while docs/OPENCLAW.md and
CHANGELOG.md now reference paths inside it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: sync TODOS.md ship dispatch entry with the v1.76.0.0 contract

Codex doc-review finding: the SHIPPED entry for /ship auto-invoking
/document-release still described the four-key JSON contract. Adds the
decisions key (console-printed, never PR markdown), the
GSTACK_SESSION_KIND=spawned dispatch marking (#2733), and the new
spawned-dispatch gate E2E to the proven-by list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 20:45:19 -07:00
Garry TanandClaude Fable 5 07b59e396c v1.75.0.0 feat: ponytail import wave — simplification review lens, arm benchmark, reuse ladder, instruction-tier digest (#2722)
* feat(autoplan): eng review always runs last — the gate reviews the final amended plan

Reorder the pipeline to CEO -> Design (if UI scope) -> DX (if developer-facing
scope) -> Eng. The old order (CEO -> Design -> Eng -> DX) let DX findings land
AFTER the required gate signed off, so eng validated a stale plan.

Accept-all semantics made explicit: every AskUserQuestion resolves to the
recommended option; premises no longer pause the pipeline mid-run (clearly-wrong
ones queue as User-Challenge items at the single Final Approval Gate). Eng's
Codex voice now sees the DX consensus summary. New free static test pins the
order; the chain E2E gains DX-between and Eng-terminal assertions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(review): simplification specialist — advisory over-engineering lens with ponytail's tag vocabulary

New 8th Review Army specialist (DIFF_LINES > 100, --simplification force flag)
hunting unrequested STRUCTURE only: delete/stdlib/native/speculative/shrink
closed tags, one-line findings, lines_removable field. speculative: replaces
ponytail's yagni: tag — we import the lens, not the posture; coverage stays
sacred (Completeness Gaps owns it, suppressions inlined, shrink needs >=5 lines).

Advisory carve-out in the merge step: advisory findings are excluded from
quality_score and the findings-count header, render with an [ADVISORY] label,
and are ASK-only in Fix-First. Zero-findings case prints the lens-scoped
'Simplification: lean already — nothing to cut.' from the PARENT (the
specialist keeps the exact NO FINDINGS contract); with findings, the parent
prints 'net: -N lines possible' summed from lines_removable.

Tests: static pins for the carve-out + early-out contract (gen-skill-docs),
two periodic e2e cases with planted fixtures — activation (over-build traps:
hand-rolled Intl, one-impl abstract, dead config) and false-flag precision
(a lean ETHOS 'choose A' diff must yield NO FINDINGS).

Inspired by dietrichgebert/ponytail's /ponytail-review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(preamble): reuse ladder in Search Before Building — rungs 2-5 of ponytail's ladder, completeness kept

Tier-3+ skills gain a per-edit reflex the section only stated as research
discipline: before writing new code, stop at the first rung that holds —
repo helper, stdlib, native platform feature, installed dependency — then
build the COMPLETE version of what remains. The closing clause is the
explicit reconciliation with Boil the Ocean: the ladder governs structure,
never coverage. Rungs 1/6/7 (YAGNI / one line / minimum that works) are
deliberately NOT imported.

Also ports ponytail's root-cause rule: one guard in the shared function
beats a guard in every caller.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(preamble): bounded-closer output rule for tier-2+ skills

After completing work, skills report in a few short lines — what changed,
what was skipped, what to watch — and cut any explanation that outgrows the
change. Explicit exemptions protect every mandated output: decision briefs,
completion-status blocks, user-requested explanations, and report-shaped
skills' report formats (the report IS the work in /qa-only, /plan-*-review,
/retro, /document-generate).

Rationale is signal-to-noise, not tokens: ponytail's own benchmark shows
terse prose alone doesn't cut cost (caveman arm: -20% LOC, +7% tokens), and
independent replications found its 'skipped on purpose' essays ate the code
savings. Includes a good/bad closer example pair per the model-overlay
guidance that a positive example beats a 'don't be verbose' instruction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(resolvers): terse-mode savings claim matches measurement — 2.6KB, not 3-5KB

Measured on the v1.71 render: --explain-level=terse saves exactly 2,611 bytes
per tier-2+ skill. The old ~3-5KB claim predated the preamble restructuring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(retro,preamble): gstack-shortcut debt ledger — accepted shortcuts leave a joined trail

When the user accepts an option that is BOTH Completeness <= 7 AND a
durable-scope call, the decision ledger entry (gstack-decision-log, ceiling +
upgrade trigger in the rationale) is the source of truth, and the agent marks
each cut corner in code with gstack-shortcut(dec-<id>): <ceiling>, upgrade
when <trigger> — same edit, no follow-up question, never agent-initiated.

/retro Step 11.5 harvests markers into a debt ledger (grep || true — zero
matches is the healthy case; skill installs and docs excluded), joins on the
decision id so nothing double-counts, tags unlinked and no-trigger rot risks,
and closes with 'N markers, M with no trigger.'

/review suppressions: a marker with ceiling+trigger downgrades a would-be
Completeness Gaps finding to acknowledged debt. Redaction test pins that the
marker ships untouched (the ledger is the point) — it does not match the
TODO(owner) hygiene shape.

Format from dietrichgebert/ponytail's ponytail-debt; store inverted to gstack's
existing decision ledger.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: refresh golden ship baselines after preamble additions (reuse ladder + bounded closer)

The golden-file regression test pins the rendered ship skill byte-for-byte;
the WS3/WS7 preamble sections are deliberate changes, so the baselines
re-capture per the goldens' own update protocol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(hosts): instruction-only tier — a 2KB committed rules digest any agent host can read

New agents-digest/gstack-AGENTS.md (1,765 bytes, hard 2,048-byte budget):
gstack's ethos one-liners, the reuse ladder, and voice rules for hosts with
no install arm — Zed, Amp, Jules, or any AGENTS.md-reading agent. Generated
by scripts/gen-agents-digest.ts, auto-refreshed by gen:skill-docs, committed
like llms.txt so setup's explainer arms can point at it before any toolchain
exists. First line carries the gstack version as its own staleness nudge.

Delivery is print-path + user-performed copy ONLY: setup never writes or
overwrites a user's AGENTS.md (a test pins this — no cp/ln/mv/redirect into
AGENTS.md anywhere in setup). openclaw and hermes explainer arms print the
path; slate keeps routing to the full Claude install and gbrain ships from
its own repo. HostConfig gains the optional install.instructionTier slot,
declared by both instruction-tier hosts. README host table now matches what
setup actually does.

Inspired by dietrichgebert/ponytail's instruction-tier AGENTS.md fallback —
one generated source, never per-host hand copies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(preamble): AskUserQuestion repetition cut — gated, passed NOT-WORSE A/B

Removes the duplicate statements v1.71's compaction left in the
AskUserQuestion Format section: the completeness rule restated in the prose
triad, the auto-decide marker syntax stated twice, the Conductor-flakiness
explanation stated twice, and the self-check's full triad restatement. Every
verbosity floor and all 14 format pins stay (Layer 0 green).

The gate this decision rested on ran before landing (new periodic
skill-e2e-auq-repetition-cut-ab.test.ts, pre-cut ref 3263fffe vs this
render, same harness as auq-verbose-vs-carved-ab): POST 7/7 format elements,
substance 5 — identical to PRE. No degradation; the load-bearing-repetition
hypothesis did not hold for these duplicates.

Net: -236 bytes per tier-2+ skill (~9.7KB corpus). Golden ship baselines
re-captured for the deliberate change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(evals): with-skill vs without-skill arm benchmark — measures whether gstack's behavioral layer earns its tokens

Ponytail's honest-benchmark method pointed at gstack itself: 3 build-shaped
tasks (native-platform over-build trap, CRUD endpoint, bug fix with planted
decoys) x 2 arms, real claude -p sessions, scored on the git diff left
behind. A research instrument, not a release gate — no assertion compares
arm scores.

Arms use the PROVEN project-scope pattern: the with-arm installs a
build-discipline skill (extracted reuse-ladder + bounded-closer content, not
whole-file copies) into the fixture's .claude/skills/ with a CLAUDE.md
routing line and an explicit invocation; a live spike confirmed claude -p
discovers and invokes project-scope skills via the Skill tool (3 turns,
exact-output probe). Fixtures are git init + local bare origin; diff capture
is three lines of git, no worktree machinery.

Failure taxonomy: zero-diff arms are VALID scored cells (deterministic
0/none, no API call), harvest failures record harvest:null, judge_error
cells are excluded from aggregates but named in the report — nothing drops
silently. armJudge: fixed sonnet judge, 0-3 unrequested-structure rubric,
must name the construct or say none, bounded retry-on-malformed; callJudge
gains optional temperature/max_tokens (defaults unchanged). recordE2E now
populates tokens_used for every E2E. Eval schema v2: harvest gains
{insertions, deletions, net}, tolerant reads keep v1 runs comparable.

Registered periodic in E2E_TIERS + touchfiles (with the auq-repetition-cut
A/B); periodic detach timeout raised to the new shard-census floor. Free
selftest (8 tests, zero API) pins fixtures, extraction, arm asymmetry, diff
capture, judge plumbing, and the retry bound.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: absorb the ponytail-import wave into the guard fixtures — ceilings, schema pin, triad phrasing

Skeleton ceilings re-captured for the 17 carved skills the wave deliberately
grew (reuse ladder + bounded closer + shortcut trail, net of the gated -236B
AUQ cut), each with its measured size in the comment per the carve-guards
protocol. eval-store schema pin updated to v2 (harvest gains
insertions/deletions/net). The AUQ prose-triad keeps its pinned per-choice
phrasing ('explicit on EACH choice') while still deferring the score scale to
the canonical Format rule — the shipped cut is strictly closer to the pre-cut
text than the render that already passed the NOT-WORSE gate. Autoplan carve
anchors follow the Phase 2.5 renumbering. Golden ship baselines re-captured.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: observability partial-file pin follows eval-store schema v2

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(test-runner): GSTACK_FREE_JOBS + opt-in flaky-retry pass for syscall-supervised sandboxes

GSTACK_FREE_JOBS overrides the computed shard count (the free runner's
analogue of the paid runner's EVALS_JOBS). On Vercel sandboxes, PID 1
installs a seccomp filter whose supervisor spuriously fails access(2) for
busy processes — measured: 200/200 git-init probes fail 'Cannot access work
tree: Permission denied' while the suite runs at 6 shards, 0/200 idle;
statx succeeds while access fails on the same path in the same process.
One serial mega-shard maximizes per-process pressure and fails too; 2
shards is the measured sweet spot.

GSTACK_FREE_RETRY_FLAKY=1 (default OFF — dev boxes should see flakes)
re-runs attributed failures once, serially, capped at 5 files; a clean
retry downgrades to a loud FLAKY-PASS naming the offenders, a repeat
failure stays red, timeouts and unattributed failures never retry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(browse): portable temp paths — TEMP_DIRS allowlist, tmpdir()-based test files

Local path validation now accepts os.tmpdir() alongside the classic /tmp
(new TEMP_DIRS in platform.ts): on macOS os.tmpdir() is /var/folders/...,
and TMPDIR-honoring CI/sandbox environments point it elsewhere entirely —
both are legitimate scratch space. Remote file serving (TEMP_ONLY) stays
pinned to TEMP_DIR alone; no change to the exfil boundary.

commands.test.ts drops 41 hardcoded /tmp literals for a tmpp() helper on
os.tmpdir() (two message assertions now reference the same variable), and
path-validation's symlink-escape test targets /etc/hosts instead of
/etc/crontab — the target must EXIST for realpath to resolve the link (a
dangling target falls back to the link's own path and passes vacuously),
and /etc/crontab is absent on Amazon Linux.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(config): portable sha256 — Linux ships sha256sum, not shasum

resolve-user-slug and endpoint hashing exited 127 on Amazon Linux (shasum
is a macOS/perl tool). New _sha256_hex helper prefers sha256sum and falls
back to shasum, matching gstack-verify-gate's existing pattern; both call
sites converted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(next-version): only trust ls-remote when origin is actually configured

Without the guard, git DWIMs the literal 'origin' as an ssh host/path; on
hosts whose transport launders exit codes the probe 'succeeds' with zero
branches and the allocator silently sees an empty queue — the exact
duplicate-allocation failure (#2545) fetchGitClaimed exists to prevent.
git remote get-url origin gates the probe; absence falls through to the
existing local-refs path with its staleness warning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(testing): sandbox-doctor — one command makes a cloud sandbox run the suite green

Measured failure taxonomy for Vercel/Conductor sandboxes (missing /dev/fd,
64M /dev/shm, seccomp-supervisor access(2) EACCES under load, uid-1000
processes with FULL capabilities defeating chmod-denial tests, no X server,
no git identity, Conductor git-shim exit-code laundering) plus the
idempotent script that treats all of it and seeds the run recipe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(config): converge on main's self-contained sha8_of — its tests extract the function standalone

The merge kept a branch-local _sha256_hex helper; main's v1.72 landed the
same portability fix inline WITH tests that extract sha8_of()'s text and run
it under a shim-only PATH — a helper call can't satisfy that shape. Adopt
the landed implementation at both hash sites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: coverage for GSTACK_FREE_JOBS override and failingFiles attribution

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: coverage for TEMP_DIRS widening and remote-serving TEMP_ONLY asymmetry

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: coverage for gstack-shortcut marker grammar and retro harvest joint

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: coverage for sandbox-doctor shell syntax and idempotency guards

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test-runner): empty-shard outcome carries failingFiles; harden flaky-retry list

The empty-shard early return omitted the (required) failingFiles field —
tsc TS2741 — feeding undefined into the flaky-retry flatMap. Also drop the
dead 'else if (worst !== 0)' guard (the enclosing if already pins it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(release): version-bump write regenerates the version-stamped agents digest

agents-digest/gstack-AGENTS.md embeds VERSION in its first line and is
byte-freshness-gated (test/agents-digest.test.ts + Skill Docs Freshness CI),
but nothing in the release path regenerated it — every version-bumping ship
of this repo would land red. write now spawns the repo's own generator when
present (agentsDigest true/false/null in the output JSON), and ship's
evidence gate allow-lists the digest alongside VERSION/package.json.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(setup): instruction-tier explainer prints the script-anchored digest path

$(pwd) printed a nonexistent path when setup ran from any other directory;
both arms now share one print_instruction_tier() using SOURCE_GSTACK_DIR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(digest): broaden AGENTS.md writer tripwire; pin digest-resolver ladder lockstep

The print-path-only guard now catches tee/install/rsync/dd/truncate, >>
appends, and laundered variable-destination writes. New test ties the
digest's hand-rendered reuse-ladder text to the preamble resolver so an
edit to either fails CI instead of shipping drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(retro): shortcut harvest drops placeholder markers and convention docs

The Step 11.5 grep matched documentation mentions (dec-<id>, dec-*) in
checklists, resolver sources, and convention tests, reporting phantom debt
rows on gstack itself. A trailing filter kills placeholder forms; prose
tells the agent to discard convention-quoting hits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(review): advisory findings count in per-specialist stats

Without this, simplification (all-advisory by construction) would log
findings:0 every run and auto-gate itself into permanent silence after 10
dispatches. The advisory carve-out governs score and header only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(evals): arm-benchmark harvest and judge hardening

- Harvest diffs against the recorded seed SHA (origin/main is movable by an
  agent that commits AND pushes; a recorded SHA is not).
- Fixtures get a node_modules .gitignore and the git wrapper a 64MB
  maxBuffer, so a vendored-dependency arm is scored instead of killing the
  cell.
- The judge diff cap is a named constant with loud truncation (log +
  judge_reasoning suffix).
- Judge prompt block markers carry a per-call random sentinel, so a diff
  containing a faked closing marker cannot escape the data block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(evals): AUQ A/B vendored pre-cut arm + judge-error inconclusive taxonomy

- The PRE arm read a branch-local SHA (3263fffe) that becomes unreachable on
  fresh clones after the squash-merge; the pre-cut render is now a vendored
  fixture.
- A judge failure on one side no longer coerces substance to 0 (which
  fabricated DEGRADATION on POST-side failures and masked regressions on
  PRE-side failures): null substance = inconclusive, format still gates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: regression pin for the originConfigured guard vs laundering git shims

On healthy hosts the guarded and unguarded paths behave identically, so a
revert passes the suite; only a shim that makes 'git ls-remote' exit 0 with
empty output (the Conductor wrapper's observed behavior) exposes it. Pins
that the empty 'successful' probe is never trusted as an empty queue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(sandbox-doctor): missing /dev/shm no longer aborts the doctor under set -eu

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(touchfiles): close dep-list gaps for the new evals

- arm-benchmark entries gain ship/SKILL.md (buildBehavioralSkill extracts
  sections from the rendered ship skill)
- review-army-simplification entries gain their planted fixtures + test file
- auq-repetition-cut-ab gains llm-judge.ts and the vendored PRE fixture

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: re-capture context-budget fixture — lock the WS6-3 reduction and Step 9 deltas

Per the ratchet protocol: the AUQ repetition cut shrank per-skill eager
tokens but the fixture was never re-captured, leaving the win unlocked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(release): digest regen is an explicit --regen-digest opt-in, not presence-sniffed code exec

Review (security) caught the cycle-1 fix executing any repo's
scripts/gen-agents-digest.ts on plain 'write' — arbitrary code exec from a
hostile clone on a routine bump, contradicting the binary's own containment
posture. The regen still runs the TARGET repo's generator (a 'trusted' copy
beside the binary would false-red the freshness gate on version drift), but
only under the flag: /ship passes it deliberately, in a repo whose code the
operator already executes (its test suite). Plain write is side-effect-free
again. Also: uniform output shape (agentsDigest: null on the JSON-manifest
branch), a REAL generator round-trip test replacing the misnamed lockstep
check, and land-and-deploy's evidence gate gets the same digest allow-path
as ship so the two grading surfaces agree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test-runner): flaky-retry vetoes on ANY unattributable failure evidence

The gate equated 'some failure attributed' with 'all failures attributed': a
shard with one attributed failure plus a headerless failure, an unhandled
error between tests, or a truncated run (no terminal summary) qualified for
retry — re-running only failingFiles and masking the rest as FLAKY-PASS,
re-opening the silent-truncation hole the strict classifier closes.
FreeShardOutcome now carries unattributedFailures; nonzero vetoes the retry.
Pins: mixed shard, truncated-with-attributed shard, empty-shard field values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(next-version): a configured origin advertising zero heads is never trusted

The originConfigured guard covered only the no-origin laundering case. With
origin configured (the normal Conductor worktree state), the laundering shim
makes a failed ls-remote exit 0 with empty stdout — read as 'the queue is
empty', the exact duplicate-allocation bug (#2545) one layer up. A reachable
remote always advertises at least its default branch, so an exit-0 zero-head
probe now falls back to local refs/remotes/origin with a laundering-specific
warning. Regression test shims git for both configurations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(sandbox-doctor): loud on git-shim patch drift; document the retry-contract override

- The /conductor/bin/git patch was a silent no-op if the shim's bytes drift
  from the exact pattern — now warns that laundering is NOT fixed.
- The bashrc block documents why GSTACK_FREE_RETRY_FLAKY=1 deliberately
  overrides the runner's default-OFF contract on this sandbox, and how to
  undo it.
- Test pins the guarded shm form (missing /dev/shm must not abort set -eu).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(digest): pin the script-anchored explainer path; catch declaration-prefixed writers

- Asserts $SOURCE_GSTACK_DIR/agents-digest path and forbids $(pwd)/agents-digest
  (the cycle-1 fix was revertible without failing anything).
- The laundered-assignment arm now matches local/export/declare/readonly/typeset
  prefixed assignments — the likeliest in-function writer shape in setup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(evals): arm-benchmark selftest runs FREE on every PR

The selftest lived inside the paid skill-e2e-* file, so fixture-integrity
and plumbing pins executed weekly at best — a broken fixture would ship past
every gating check and be discovered when the periodic run burned money on a
dead instrument. Harness extracted to test/helpers/arm-benchmark-harness.ts,
selftest to test/arm-benchmark-selftest.test.ts (free suite). Touchfiles:
harness added to the three benchmark dep lists; the auq-repetition-cut-ab
tier comment now states the MANUAL re-run obligation honestly (periodic runs
force EVALS_ALL, so dep lists cannot auto-trigger it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: re-capture context-budget fixture after cycle-2 template deltas

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(sandbox-doctor): keep both heredoc bodies under the 512B pipe-deadlock window

The cycle-2 additions pushed the python-patch and bashrc heredocs into the
512-65536B window test/heredoc-pipe-deadlock.test.ts guards (sh scripts get
no BASH_COMPAT escape hatch). Same content, tighter prose; the drift warning
now reuses the patch pattern variable instead of a second literal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(review): a gstack-shortcut marker only suppresses findings when its decision id resolves in the ledger

Cross-model catch (Claude adversarial + Codex agreed): any diff author could
fabricate a marker and silence Completeness review of that gap. Reviewers
now resolve the dec-id via gstack-decision-search; an orphan marker is
reported as a forged suppression, not honored as debt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(autoplan): define the B2 gate path — accepted premise challenges amend the plan and re-run Eng

The final gate offered B2 (respond to User Challenges) but the option
handler table omitted it, leaving accepted challenges with no amendment or
Eng re-review path. B2 now walks challenges one at a time; an accepted one
amends the plan and re-runs Eng (the gate always reviews the final plan),
sharing D's 3-cycle cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(evals): arm benchmark runs each fixture's functional oracle — correctness before LOC

The plan's metric order is diff-quality FIRST, but cells never ran the
fixtures' own run-tests.js, so a refusal, a broken implementation, and
working code were indistinguishable in aggregates (Codex adversarial catch).
Tasks with an oracle declare checkCmd; every cell records checks=pass|fail|none
in the report line and eval store. Selftest pins the oracle declarations and
that the planted bug fails its own check pre-fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ship): check the bump's agentsDigest result; state the --regen-digest trust envelope honestly

A failed digest regen warned and moved on — ship now instructs re-running
the generator and staging the digest with the bump (the freshness check
stays red otherwise). The 'no-op everywhere else' phrasing oversold safety:
the step now names what executes and why that is inside the envelope Step 5
already opened (the repo's own test suite).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test-runner): GSTACK_FREE_JOBS accepts digits only — parseInt truncation defeated the loud-failure contract

'2abc' silently became 2 and '3.7' became 3 despite the error text claiming
a positive-integer requirement. Strict /^\d+$/ pre-check; both shapes pinned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(sandbox-doctor): atomic git-shim patch, :99-socket Xvfb check, dnf gate, non-interactive sudo

- The /conductor/bin/git patch writes tmp-then-rename with a .orig backup —
  a concurrently spawned git can never exec a truncated shim.
- Xvfb running-check looks for the :99 socket, not any-display pgrep.
- Xvfb install is dnf-gated so non-dnf distros degrade to a warning instead
  of aborting the remaining fixes under set -eu.
- The bashrc /dev/fd restore uses sudo -n || true — no password prompt at
  every shell start on non-passwordless machines.
- BASH_COMPAT=50 keeps heredoc bodies off the bash pipe window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(build): a failed agents-digest regen fails gen-skill-docs instead of deferring the red to CI

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(browse): an untrustable TMPDIR (/, $HOME, a cwd ancestor) never widens the local allowlist

TEMP_DIRS honors os.tmpdir() at daemon start; a daemon launched with
TMPDIR=/ would have trusted the whole filesystem for local path validation
for its lifetime. Subprocess pins cover /, $HOME, cwd-ancestor rejection and
that a benign distinct TMPDIR (the sandbox recipe's $HOME/tmp) stays honored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: zero-heads warning names the benign cause too; digest path declaration made load-bearing; ratchet re-capture

- The ls-remote zero-heads warning no longer accuses an empty remote of
  running a laundering shim.
- instructionTier.rulesFile now must equal the generator's DIGEST_RELPATH
  (and setup must print it) — the declaration fails with the real path
  instead of lying silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: file ship-time follow-ups in TODOS

skillify HOME-override gate red (pre-existing, proven on main), the
auq-verbose-vs-carved-ab branch-local ref, eval-store harvest union,
evidence digest allow-path scoping, and the WS6-2 dead-frontmatter live-host
verification deferral.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* v1.73.0.0 chore: version bump + CHANGELOG — ponytail import wave

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: raise ship skeleton parity ceiling — measured 75,592 after the v1.73 release-step prose

The --regen-digest trust-envelope paragraph (Step 12) and the evidence-gate
digest note (Step 16) grew the ship skeleton past the previous 75,420
ceiling. Re-measured per the deliberate-change protocol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: update project documentation for v1.73.0.0

- README.md, docs/skills.md, AGENTS.md: /autoplan phase order corrected to
  CEO → design → DX → eng (eng always last); /review rows note the advisory
  simplification lens
- docs/PROJECT_STRUCTURE.md: add agents-digest/, gen-agents-digest.ts,
  sandbox-doctor.sh, test-free-shards.ts to the annotated tree
- CONTRIBUTING.md: document GSTACK_FREE_JOBS, GSTACK_FREE_RETRY_FLAKY, and
  the sandbox-doctor one-command fixer in the Tier 1 test section

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: apply cross-model doc-review fixes for v1.73.0.0

- README.md: host table gains the OpenClaw explainer arm row (setup has the
  arm; the table claimed to match setup)
- docs/skills.md: /review completeness-gaps section documents the
  gstack-shortcut(dec-<id>) acknowledged-debt suppression and orphan-marker
  flagging; /autoplan deep-dive states the recommended-option default with
  the 6 principles as tie-breakers
- CONTRIBUTING.md: host count 8 -> 10 (Hermes, GBrain), supported-hosts list
  completed
- docs/TESTING_INTERNALS.md: sandbox recipe says to source ~/.bashrc after
  the doctor seeds it; GSTACK_FREE_JOBS wording fixed from "caps" to
  "overrides in either direction" (matches the un-clamped runner)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): temp-dirs asymmetry pins are topology-aware; TMPDIR probes are POSIX-only

CI exposed two wrong assumptions in the new temp-dirs tests, neither a
product bug:

- The remote-serving asymmetry test assumed a distinct os.tmpdir() lies
  OUTSIDE TEMP_DIR, but the free-shard runner nests each child's TMPDIR
  inside /tmp on CI — a file there is under TEMP_DIR, so serving it
  remotely is legitimate. The test now pins the actual exfil boundary on
  every topology (a cwd project file is locally readable, never remotely
  servable) and branches the os.tmpdir() case on nested-vs-outside.
  Reproduced locally with TMPDIR=/tmp/nested-tmp before fixing.

- The untrustable-TMPDIR subprocess probes set TMPDIR, which Windows
  os.tmpdir() ignores (reads TEMP/TMP) — and on Windows TEMP_DIR is
  DEFINED as os.tmpdir(), so the fixed+movable two-dir topology the guard
  filters does not exist there. Probes now skip on Windows with that
  rationale; the benign-TMPDIR assertion compares realpaths.

Verified under all three POSIX topologies: TMPDIR=$HOME/tmp (outside),
TMPDIR=/tmp/nested-tmp (CI shard shape), TMPDIR unset (identical).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(build): DIGEST_RELPATH is a forward-slash literal on every platform

path.join built it with backslashes on Windows, so the wiring test's
string comparisons against setup and hosts/*.ts (which carry the
forward-slash literal) could never match there — windows-free-tests red.
path.join(root, DIGEST_RELPATH) at the write site normalizes fine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(sandbox-doctor): bashrc block re-heals the /dev/shm remount on sandbox restart

The 4G remount does not survive restarts; a reverted 64M shm made the
multi-tab browse handoff test fail consistently under suite concurrency
(observed live: two consecutive full-run failures, green in isolation,
green again after remounting). Same guarded arithmetic as the doctor body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): close the cross-shard porcelain race that failed Windows CI

Two-part fix for the gen-skill-docs-out-dir isolation-pin failure:

- cookie-import-browser built its scratch cookie DBs inside the TRACKED
  browse/test/fixtures/ dir (created in beforeAll, deleted in afterAll), so
  they flash as untracked files mid-run — a concurrent shard's porcelain
  snapshot caught the window on Windows. The DBs now live in a per-run
  tmpdir; zero source-tree writes.

- gen-skill-docs-out-dir is the free suite's only LIVE porcelain-snapshot
  test, so it joins TREE_MUTATING (the serial quiet window): any concurrent
  transient tree-write can race it, and its own spawned render rewrites
  llms.txt/agents-digest in place (idempotent on a fresh tree).

The race is pre-existing; this branch's +5 test files reshuffled shard
composition and exposed it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* v1.75.0.0 chore: queue-advance rebump — perth-v2 landed v1.74.0.0 on main

The v1.73.0.0 slot this branch claimed was superseded when #2721 merged;
same MINOR level relative to main per the versioning invariant. CHANGELOG
entry renumbered (1.73.0.0 was branch-internal and never landed on main),
digest restamped via --regen-digest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test-runner): duration-packed walls keep the per-file floor — predictions don't transfer across machines

The committed duration seed is recorded on fast CI; a syscall-supervised
sandbox replays the same files 2-4x slower. Observed post-merge: a 253-file
shard predicted ~242s was wall-killed at its predicted-x3 725s wall while
genuinely progressing (the old count heuristic guaranteed 1265s). Packed
walls may be looser than the count floor, never tighter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 10:10:35 -07:00
b1485d8897 v1.74.0.0 test/CI overhaul: green means green, suites restructured for speed (#2721)
* fix(ci): free-tests lane actually runs the make-pdf e2e gates

The 9 make-pdf/test/e2e gate tests probe make-pdf/dist/pdf,
browse/dist/browse, and the diagram-render bundle, then self-skip when
absent. The required free-tests lane never built any of them, so the
gates silently skipped on Linux for their entire life (verified: 9 of
14 skip, exit 0). make-pdf-gate.yml's justification for deleting its
Linux leg claimed the free lane covered this — it didn't.

- new build:gates script: exactly the three artifacts the gates probe
  (full bun run build compiles five binaries; ~60-90s tax on the only
  required check is not warranted)
- free-tests.yml: build:gates step + poppler-utils +
  fonts-noto-color-emoji (fonts must precede the first browse daemon
  launch — Chromium snapshots fontconfig at startup; verified live:
  a warm daemon renders tofu, a fresh one embeds NotoColorEmoji)
- make-pdf/test/e2e/ci-prereqs.test.ts: GSTACK_EXPECT_BINARIES=1 (set
  by the workflow) inverts the skip polarity in CI — dropping the
  build step or poppler fails the lane instead of re-opening the
  silent-skip hole

Pre-flight: all 9 gates green on Linux locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): kill the three zero-test eval jobs (hollow green)

- delete the vestigial e2e-codex / e2e-gemini matrix rows: both files
  are whole-file periodic-tier, so with no row tier: they ran ZERO
  tests and reported green on every PR (~2 min of runner each, pure
  false confidence; the periodic lane owns those suites)
- e2e-pty-plan-smoke gains tier: gate — its two files are whole-file
  describeE2ETier('gate'), so the job burned ~7 min of container setup
  then skipped every describe
- KNOWN_TIER_UNSET burned down to empty; the ratchet stays armed so a
  future row/file tier mismatch fails the suite instead of shipping
  hollow green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): least-privilege permissions + fork-safe concurrency keys

- evals.yml / evals-periodic.yml evals jobs: explicit contents:read +
  packages:read (container-image pull) and persist-credentials:false —
  the jobs that execute PR-authored code with three provider API keys
  ran on the repo-default token grant with the token written into
  .git/config
- permissions blocks for the 4 workflows that had none (skill-docs,
  make-pdf-gate, windows-free-tests, windows-setup-e2e)
- fork-safe concurrency keys: actionlint, skill-docs, make-pdf-gate,
  windows-setup-e2e switch from head_ref to PR-number keying — a bare
  branch name carries no fork prefix, so same-name branches from two
  forks shared one group and cancelled each other's runs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): one bun version everywhere + drift tripwire

Lanes disagreed four ways: 1.3.13 (free-tests, windows, Dockerfile.ci),
latest (quality-gate, make-pdf-gate), unpinned (skill-docs,
version-gate — setup-bun installs latest), 1.3.10 (.gitlab-ci.yml).
Different Bun versions change the runner output shapes the strict
classifiers regex-match, spawn semantics, and shell parsing — a lane on
a different Bun tests a different product; Dockerfile.ci's own comment
records this class biting once already (silent 1.3.13/1.3.14 drift).

All surfaces pinned to 1.3.13; test/bun-version-drift.test.ts scans
every workflow setup-bun stanza + Dockerfile.ci + .gitlab-ci.yml and
fails on any mismatch or unpinned stanza. skill-docs also gains
--frozen-lockfile (was bare bun install).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(ci): bind the three-way image-tag hashFiles() expressions

evals.yml, evals-periodic.yml, and ci-image.yml each compute the CI
image tag from hashFiles('.github/docker/Dockerfile.ci', 'bun.lock',
'patches/**') — synced by comment only (TODOS.md 'CI three-way
image-tag drift'). If one input list drifts, that workflow computes a
different tag for the same content: eval lanes silently rebuild the
image every run, or ci-image prebuilds a tag nobody looks up. The test
extracts each tag-computation site and fails on any mismatch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): ci-image stops rebuilding the identical image every ship

- package.json out of the trigger paths: the tag hash deliberately
  excludes it (version bumps every ship), so every merge rebuilt and
  re-pushed the IDENTICAL tag (~2m26s for zero content change);
  patches/** added (it IS a tag input)
- manifest existence check (mirrors evals.yml): tag already exists →
  skip the build
- concurrency group: two rapid main pushes raced pushing the same
  :latest/:buildcache tags
- cron staggered 06:00→04:00 Monday: it shared the exact minute with
  evals-periodic, which could race a half-pushed tag or duplicate the
  build
- timeout-minutes: 30 (was unbounded → 360-min default for a hung
  docker build)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): quality-gate drops the 74s full-history checkout

fetch-depth:0 cost 74 of the job's 92 seconds; the three gates it feeds
take ~12s combined. Shallow checkout + exact-SHA fetches for the diff's
base/head (an exact-SHA fetch, not a guessed depth — long-lived
branches and merge queues still resolve), with a --deepen fallback for
push events whose 'before' is unusable. timeout right-sized 20→10 min.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): small-lane batch — timeouts, right-sizing, windows cache warm-start

- timeout-minutes on the 6 remaining unbounded jobs (actionlint 5,
  skill-docs 10, version-gate 10, make-pdf-gate 15, pr-title-sync 5,
  evals build-image 15) — a hung step sat on GitHub's 360-min default
- right-size measured-over-long timeouts: dependency-review 10→5,
  windows-setup-e2e 15→10
- dependency-review: 2-core runner (28s API call on an 8-core box) and
  drop .github/workflows/** from its trigger paths (workflow edits have
  no dependencies to review)
- windows caches gain restore-keys: a lockfile bump paid the 26s/43s
  restore for a guaranteed cold miss

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): scope GSTACK_HOME to each file's execution window

Five files assigned process.env.GSTACK_HOME at module scope. Shard
processes evaluate sibling modules before running their tests, so the
assignment leaked into every other file in the shard — the damage was
already visible in defensive workarounds (relink.test.ts:28 'fresh
install test saw a neighbor's skill_prefix'; cdp-e2e's own comment
documents a sibling's temp dir baked into artifacts).

Pattern: save original, assign in beforeAll, restore in afterAll
(cdp-e2e already restored but still assigned at load — its window now
matches the others). GSTACK_TELEMETRY_OFF and GSTACK_PROJECT_SLUG get
the same treatment where they rode along. Victim files' defenses stay
in place (cheap insurance).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: tripwire against module-scope GSTACK_HOME assignments

Column-0 assignment of GSTACK_HOME / GSTACK_STATE_ROOT in any tracked
*.test.ts fails with the file:line and the fix (beforeAll + afterAll
restore). Kills the cross-file env-leak class the previous commit
swept.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): e2e-harness-audit derives its skill census from disk

The hand-maintained 39-name SKILL_GLOBS list had drifted to 39 of 54
SKILL.md.tmpl on disk. No live gap today (none of the 15 unlisted
skills is interactive), but the next interactive skill would have
landed unguarded with zero signal. The audit now walks top-level dirs
for SKILL.md.tmpl (statSync so symlinked dirs like connect-chrome
count), so new skills are in scope the commit they appear.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(evals): judges honor the eval-model resolution chain + real 429 backoff

callJudge inlined GSTACK_EVAL_MODEL_JUDGE || sonnet, silently ignoring
the global GSTACK_EVAL_MODEL override every other eval call site honors
via lib/eval-model.ts. New 'judge' kind in DEFAULTS (sonnet — the D1a
pin-on-regressors calibration stands; model CHOICE unchanged) and
callJudge resolves through it: explicit arg > GSTACK_EVAL_MODEL_JUDGE >
GSTACK_EVAL_MODEL > default.

429 handling upgraded from one fixed 1s retry (reliably lost races at
CI concurrency) to three jittered exponential retries (~1s/4s/16s),
honoring the server's retry-after when present.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): the two expect(true) paid stubs become test.todo

skill-e2e-spec-execute (600s budget) and skill-llm-eval-spec (300s)
reported PASS on every periodic run while asserting nothing. Deleting
them would remove the periodic-tier selector surface they exist to
register (diff-based selection for spec/ changes), so they become
test.todo — reported as todo/skip, never pass — with the v1.1
implementation specs kept in-file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): reactivate 5 quarantined browse tests (2 security)

extension-sender-auth's two privileged-message denial tests (content
script + missing sender.url — the extension's security boundary) and
snapshot's three skips were quarantined 'pre-existing' failures. Root
cause: machine-local state on the quarantining dev machines — the test
and gate code are byte-identical between the quarantining commit
(410b4928) and HEAD, and all five pass deterministically on a clean
checkout (68/68 across both files, multiple runs). No assertions
weakened, no product changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(evals): activate the 4 paid test files that could never run anywhere

carve-section-loading, codex-e2e-plan-format,
codex-e2e-recommendation-substance, and llm-judge-recommendation gated
on EVALS/tier (free suite loads them as describe.skip) but their names
fell outside PAID_TEST_GLOBS, so no paid lane ever selected them — net
execution zero, forever. The existing matrix tripwire filtered on
isPaidTestFile() first, so it was blind to exactly this class (the same
bug that hid the pre-split monolith's gate tests for ~8 releases).

- PAID_TEST_GLOBS: codex-e2e* + skill-llm-eval* wildcards (replacing
  exact names) + llm-judge-recommendation + carve-section-loading;
  package.json's six test-script glob lists mirrored
- codex-e2e-plan-format gains the explicit periodic tier gate its
  siblings carry (external-service rule) — without it the sharded
  runner's no-guard default would spawn Codex in the gate tier per PR
- eval:bg:periodic --timeout 32400→37800: the census growth pushed the
  periodic worst case to 35910s; the old value had 270s of headroom
  BEFORE this change and would now kill healthy runs mid-flight
- new test/paid-orphan-tripwire.test.ts: any EVALS/tier-gated test file
  outside the globs fails the free suite (reasoned SCANNER_EXEMPT for
  the gate helpers + meta-tests) — the class-killer
- paid-shards pins updated: the four orphans now assert INSIDE the
  census

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(browse): restrictDirectoryPermissions warns and skips symlinked dirs

Closes the Windows Free Tests red: recent lane failures showed a
platform-unguarded POSIX mode-bit assertion ('Expected: 493' — a
symlink-skip test) from PR-branch variants; the KNOWN_WINDOWS_SAFE
force-include reason ('mode-bitmask hits are POSIX-branch only') did
not hold for that shape, and main had neither the guard nor the
behavior.

- product: lstat first; a symlinked dir gets a warning and a skip on
  both platforms — chmod AND icacls dereference the link, so
  restricting through a symlink hardens an unvetted target (and
  /inheritance:r could lock out its real owner). All callers already
  treat hardening as best-effort (try/catch).
- test: the symlink regression test, platform-aware — symlinkSync in
  the house try/catch skip pattern (Windows runners without Developer
  Mode can't create symlinks), mode-bit assertion guarded off win32,
  behavior assertions (no throw, warning text, target readable)
  everywhere; POSIX still proves the skip (0o755 unchanged, not 0o700)
- KNOWN_WINDOWS_SAFE reason updated to the now-true premise

20/20 pass on Linux.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): unique tmp dirs for plan artifacts + audited live-repo cwd sites

Six paid PTY tests wrote their expected plan artifact to a FIXED shared
/tmp path ('/tmp/gstack-test-plan-<mode>.md') and rmSync'd it in
finally — under --retry 1, EVALS_JOBS>1, or two concurrent worktrees, a
sibling's cleanup deletes this run's artifact and the D19 'agent did
not produce expected plan file' assertion fires spuriously. Each test
now mkdtemps its own dir, interpolates the unique path into the agent
prompt (fixture-sourced prompts get a replaceAll + drift guard that
throws if the fixture's literal ever moves), and cleans up its own dir.

The 18 cwd:-into-the-live-repo sites were audited: all deliberate
(skill registry + hermetic pre-trusted dir, in-repo gen renders, git
history reads, slug resolution) — each now carries a
'// LIVE-REPO CWD: <reason>' comment so the next audit can tell
deliberate from accidental.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): trim the seven over-wall 1700s timeouts to the 1500s physical ceiling

1,700,000ms (28.3 min) exceeded every wall these tests run inside: the
25-min CI job timeout and the 1800s sharded-runner wall (which also
leaves --retry 1 zero room for a second attempt). Budget above the wall
is fiction, not headroom — a test that actually used it produced a
job-level kill (no bun summary, no artifact) instead of a clean
per-test timeout. No recorded p95 exists for this family (they are
being retiered to periodic in the re-platform wave); the trim stops at
the physical ceiling rather than guessing lower. Final policy lands in
the Wave-2 eval-budgets constants module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(gen): main() guard — importing gen-skill-docs no longer regenerates the tree

The generator's whole body executed at module load, so any import of it
(test/gen-skill-docs.test.ts pulls assertSinglePreamble via require();
test/catalog-trim.test.ts imports helpers) regenerated all 71 SKILL.md
in place — the root cause of half the TREE_MUTATING serial-shard
entries (hazard class #2532). The body now lives in an exported
main(): number behind if (import.meta.main).

Semantics preserved exactly: failure exits are immediate (matching the
old top-level process.exit), success leaves the event loop to drain so
the llms.txt fire-and-forget IIFE finishes its write, and the module
stays synchronous/require()-able. Proofs: byte-identical --host all
output (git status clean), --dry-run stale-tree still exits 1 (the
skill-docs freshness lane depends on it), and the new
test/gen-skill-docs-import-purity.test.ts pins load-time purity via a
subprocess probe (mtime-based, so a dirty worktree can't false-fail).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(gen): --out-dir renders every host, outputs-only

--out-dir was Claude-host-only (gen-skill-docs.ts:842), which forced
the codex/factory-regenerating tests (gen-skill-docs, skill-validation,
host-config) to mutate the live tree — the reason they sit in the
TREE_MUTATING serial shard. The flag now mirrors ALL outputs into the
out-dir: external-host trees (.agents/.factory/... via
processExternalHost), external section files, openclaw docs, and
gstack/llms.txt (a catalog-mode render must never rewrite the tracked
index). OUTPUTS ONLY — inputs (templates, sections/, host configs) are
always read from ROOT, so an empty out-dir can never feed the render.
rewriteSectionBase stays Claude-only (external hosts have their own
path grammar).

Proofs: in-place --host all is byte-identical (tree clean);
--host all --out-dir <mkdtemp> renders the full multi-host tree with
ROOT untouched; gen-skill-docs-out-dir tests + 415/415
gen-skill-docs.test.ts green (bin/dev-setup's claude rendering
byte-compat).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(evals): every E2E key's dep list names its own declaring test file

129-of-177 keys omitted their own test file, so editing only a test's
prompt or assertions selected NOTHING — the changed test never ran on
the change that changed it. 135 keys self-registered (110 E2E + 25
LLM-judge), resolved by strict declaration evidence (testName:/
testIfSelected/judge call sites), with skill-name false positives
excluded.

e2e-tier-alignment's warn-only branch for unregistered files is now a
hard failure with a 4-entry KNOWN_UNREGISTERED ratchet (template-
literal testNames, fail-open-safe) + a burn-down test so the set only
shrinks. Selection sanity: a one-file diff on skill-e2e-qa-workflow now
selects its 4 tests (was 0); skill-llm-eval 0 → 25.

Known follow-ups (filed): 15 E2E + 2 judge PHANTOM keys select tests
that exist nowhere; codex-e2e-plan-format's testIfSelected names have
no map keys (run-all only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(evals): ratchet the 8 newly-visible gate-matrix gaps

The self-registration sweep made these eight files' gate-tier keys
visible to the census for the first time — their gate tests run in NO
CI lane today (pre-existing hole, newly measurable). Ratcheted into
KNOWN_MATRIX_GAPS with the burn-down note: the paid-lane re-platform
runs every gate file by construction and retires this ratchet class.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(test): duration-aware LPT shard packing for the free suite

Hash sharding balances file COUNTS (1.15x spread) but not cost — the
Playwright-launching files landed 4/3/4/1/2/1 across 6 shards, giving a
measured 28s–97s shard spread and ~40s of idle tail on every run.
Full-suite mode now packs by recorded per-file durations
(longest-processing-time-first) when the committed seed
scripts/free-test-durations.json exists.

- ONE store, no overlay: the seed is refreshed occasionally via the new
  --record-durations mode (each file timed in its own child — exact,
  and immune to bun's stream buffering, where silent passers print no
  header to timestamp); GSTACK_FREE_TEST_DURATIONS overrides the path
  for experiments; CI never records
- seed is a hint: missing → silent hash-shard fallback; corrupt (bad
  merge) → one warning + fallback; unknown files → 75th-percentile
  pessimism so a surprise long-runner can't recreate the tail
- packed shards get duration-aware walls (max(base, predicted x 3)) —
  LPT decouples count from cost BY DESIGN, so the 5s/file heuristic
  would undersize a shard holding few expensive files
- one log line per shard (files + predicted seconds) so packing
  regressions are diagnosable from any run log
- the --shard CI-matrix path is untouched: stable hash indices are its
  contract
- successor note in-code: bun >=1.3.14 ships native --timings/--shard
  LPT — swap this packer when the repo unpins 1.3.13

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): decouple slop:diff from bun run test; quality-gate runs it per PR

'bun run test' silently appended up to two 120s npx slop-scan runs plus
a git worktree add/remove after the suite (2>/dev/null || true) —
invisible in the documented '~90-100s' timing and pure friction in the
pre-commit loop. Decoupling is not coverage removal: quality-gate.yml
now runs slop:diff on every PR (advisory, matching its in-repo 'never
blocking' contract), and /review already invokes it explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(test): eval-budgets timeout tiers + fit/ceiling policy test

Five named tiers (JUDGE 120s / CAPTURE 300s / CAPTURE_LONG 600s /
PTY 900s / PTY_LONG 1200s) replace hand-ratcheted sprawl (46x300s,
46x120s, 44x360s, 44x180s, 27x240s, 19x150s, 13x420s, 12x600s...),
much of it inflated to paper over the old 40-way in-shard concurrency
that the sharded runner's 1-file-per-shard model kills. Policy test
pins: every tier fits the shard wall minus 120s overhead (the
structural fix for budgets-above-the-wall fiction), tiers stay ordered,
and no paid literal exceeds PTY_LONG x1.25 — oversized tests get split,
not budgeted past the wall.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(test): shared runBin helper for bin-script unit tests

~36 free test files each carry a near-identical local run() (spawnSync
+ utf-8 + {status, stdout, stderr}) differing only in env composition,
cwd, and timeout. runBin absorbs the invariant core; options carry the
variance (gstackHome sets BOTH GSTACK_HOME and GSTACK_STATE_DIR — the
config-precedence trap several locals rediscovered independently; home
for $HOME-anchored bins; input/trim/timeout/maxBuffer). Free-test-only
by design so it never becomes a de facto global touchfile. Migration of
the 36 call sites lands separately (mechanical batches).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): runBin trim assertion — trim shapes stream ends, not interior

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(test): mechanical sweep — 298 paid-test timeouts onto eval-budget tiers

69 files, both shapes (trailing bun-test budgets and runner
timeout/timeoutMs options), ROUND-UP ONLY so nothing that passed can
start failing: 75 → JUDGE_MS, 137 → CAPTURE_MS, 74 → CAPTURE_LONG_MS,
9 → PTY_MS, 3 → PTY_LONG_MS. Raw >=60s literal count in the paid scope:
395 → 97, of which 51 are non-timeout noise (fixture dates, run IDs)
and 46 are enumerated justified holds (comment-carrying calibrated
budgets, poll-loop constants, utility spawn waits, and the seven
physical-ceiling 1_500_000 sites). The eval-budgets policy ratchet
keeps the residue from regrowing.

Known collapse: where an inner runner budget and its enclosing test
budget now share a tier, the old stagger is gone — an overrun surfaces
as a bun test timeout instead of a graceful runner timeout
(diagnosability trade, not a correctness one).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: coverage fill — 95 tests for six zero-coverage surfaces

- eval CLI family (eval-list/compare/summary + eval-select smoke): the
  primary interface to eval results had no tests; isolation via a fake
  gstack-slug under a mkdtemp HOME (the scripts' real resolution path —
  they do NOT honor GSTACK_EVAL_DIR; only EvalCollector does). Pinned
  current behavior: eval-list does NOT exclude _partial runs (documented
  improvement candidate)
- slop-diff (runs on every /review + quality-gate): fixture git repo +
  first-on-PATH npx stub (never downloads real slop-scan); no-diff
  early exit, missing-scanner fallback, fingerprint line-insensitivity,
  merge-base worktree scan
- bin/gstack-code-intelligence CLI arg surface (lib was covered, the
  284-line CLI wasn't): select/consent/suggest/index/search gating;
  pinned: --help routes to usage failure exit 1 (no handler)
- browse media-extract: the page.evaluate callback exercised in-process
  against a mock DOM (no exports added) — lazy-src fallback chain,
  HLS/DASH detection, bg-image url() parsing, 500-element cap
- browse session-cookie-store: factory contract (cookieName/ttlMs/
  maxSessions eviction, cross-store isolation, mint→validate
  round-trip); store is in-memory — no fs cases exist
- lib/version-source direct unit tests (gstack-version-bump.test.ts
  spawns the bin, never imports the lib): parse/format/cmp/bump
  coercion, npm 4→3 translation, #2501 mangled-JSON regression class

All hermetic (mkdtemp homes, runBin child isolation); windows curation
correctly partitions the six.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(test): first runBin migration batch (3 of ~36 run() duplicates)

explain-level-config, benchmark-cli, evidence move onto the shared
helper; each file's remaining special-case spawnSync sites (raw-buffer
probes, env-scrub probes) stay put deliberately. 55/55 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(evals): paid shards spool to disk + shared runShardChild lifecycle

- runPaidShard no longer buffers whole 30-min stream-json streams in
  RAM (x concurrent jobs): every byte tees to a per-shard log file
  (slug-named, path printed at START for mid-run inspection and on the
  FAILED terminal line); failures print a 64KiB tail read back from
  disk; passing shards stay quiet (the file is the record) — the free
  runner's proven contract. Classification unchanged: the strict
  classifier still sees every byte first.
- the ~35 duplicated spawn/group-kill/wall-timer/finally-reap lines
  move into runShardChild in test-strict-output.ts (detached-per-
  platform spawn, signal forwarding, SIGKILL group kill at the wall,
  drain-before-verdict); designed so the free runner can migrate later
- expectedFiles drift fixed toward ENFORCEMENT: the injected-command
  exemption is gone — a fake command exiting 0 without bun's terminal
  summary now reads FAILED (pinned: silent-pass → failed)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(evals): parent-computed selection propagates to shard children

The sharded runner computed diff selection once, then each of its 48-73
children recomputed it at module load — including, on touchfiles-diff
branches, a per-child bun subprocess evaluating the old data file (20s
timeout each). The parent now serializes {version, selected, reason} as
EVALS_SELECTION_JSON into the shard env; e2e-helpers adopts it at load.
Fail-open preserved: any parse/shape violation → ONE stderr warning +
local recompute; absent env → silent local compute (non-sharded
entrypoints unchanged). Drift test pins parent→child round-trip to
identical selection decisions plus the malformed/absent cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): kill the four worst fixed sleeps (300s/30s/30s/20s)

- watchdog.test: the 20s blind wait for one production parent-watchdog
  tick becomes BROWSE_PARENT_WATCHDOG_INTERVAL_MS=250 (new env knob in
  server.ts, NaN-safe, production default unchanged) + polls for the
  boot line and the tick's stay-alive log — strictly stronger (the old
  form never proved a tick observed the parent death). 24s → 3.6s.
- stop-dead-daemon / terminal-agent-owner-watchdog: the 300s/30s
  stand-in child lifetimes become stdin-EOF-bound — the child can never
  self-exit mid-test on a slow runner (spurious-failure class) and
  self-reaps instantly if the test dies (no 300s orphans). Node-compat
  stdin APIs (owner-watchdog runs on the Windows lane).
- browser-skill-commands: the sleeper fixture's 30s self-time becomes
  8s (no stdin pipe exists in runToFiles) — far above the 1s product
  timeout it must outlive, below the test ceiling, so a timeout-kill
  regression fails on clean assertions instead of an opaque bun
  timeout; added: stdout must NOT contain 'done'.

45/45 green across the four files + server tripwires.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): gen-skill-docs + catalog-trim leave the serial mutator shard

gen-skill-docs.test.ts's 15 in-place generator spawns now render into
mkdtemp out-dirs (gitignored-artifact reads repointed; the handshake
scan's silent console.warn degrade became a hard assertion); its
tracked-tree reads (freshness dry-run, SKILL.md content pins) stay
reads. catalog-trim needed no change beyond the earlier main() guard —
its import is now side-effect-free (pinned by the import-purity test).
Both TREE_MUTATING entries deleted in this commit, per the transition
rule: an entry leaves in the same commit as the file's last in-place
write.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): skill-validation renders codex host into an out-dir

Its 3 in-place --host codex regeneration sites collapse into one
module-level --out-dir render; assertions untouched. TREE_MUTATING
entry deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): host-config self-provisions goldens (ordering dependency severed)

Its goldens were 'produced by gen-skill-docs.test.ts' with a
when-missing beforeAll fallback that wrote the live tree — an
inter-test ordering dependency the serial shard hid. It now renders
codex+factory UNCONDITIONALLY into its own out-dir and reads goldens
only from there (the Claude golden deliberately keeps reading tracked
ship/SKILL.md — a read; out-dir claude renders repoint section-base
paths by design). TREE_MUTATING entry deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): gbrain-detection-override drops mutate-then-git-restore

regenAndSnapshot renders --host claude --out-dir <mkdtemp> (+
--respect-detection) and snapshots probes from the out-dir. The
git-restore machinery is deleted outright — it restored only
PROBE_FILES of the 71 files each call wrote, so a stale tree kept the
other 68 dirty (the partial-restore bug), and its 'no output-path arg'
comment had been false since --out-dir landed. TREE_MUTATING entry
deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): catalog-mode-full renders to out-dir; restore machinery deleted

The full-catalog smoke no longer rewrites all 71 SKILL.md then
regenerates to restore (with its 'CRITICAL: failed to restore' prayer
path) — it renders into a mkdtemp and additionally asserts tracked
ship/SKILL.md is byte-unchanged. TREE_MUTATING entry deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): idempotency proof strengthens to two-out-dir recursive diff

Two renders into two separate out-dirs, EVERY file diffed byte-for-byte
(claude-only and --host all; normalization only for each dir's own
sanctioned section-base repoint; presence-sanity lists guard against a
vacuous empty-dir pass) — strictly stronger than the old in-place
double-regen that sampled 5 files. TREE_MUTATING entry deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): spec-template-sync compares an out-dir render, not an in-place one

TREE_MUTATING entry deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(test): the serial tree-mutating shard dissolves — TREE_MUTATING is empty

Zero mutators remain (all eight render into out-dirs now), so the four
ratchet READERS (parity caps, size budgets, carve parity/ordering) get
a quiet tree by construction in any shard and rejoin the parallel
phase. The ~35-40s serial tail on every full-suite run is gone. The
mechanism stays: a future test that genuinely must write shared
artifacts in place earns an entry with a reason and is serialized
again; the census pin still fails on renamed keys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(gen): out-dir byte-identity + tree-clean pins for external hosts

codex render: porcelain unchanged AND out-dir gstack-ship/SKILL.md
byte-identical to a fresh in-place render (+openai.yaml presence);
--host all render: exit 0, porcelain unchanged, claude + .agents +
.factory + llms.txt + openclaw docs all present in the out-dir.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(test): commit the initial free-test durations seed (496 files)

Recorded via --record-durations on a quiescent tree: 479s serial
total, p50 92ms / p90 1.8s / max 31.4s — the top-heavy cost shape LPT
packing exists for. A hint, not a contract: refresh opportunistically
with bun run test:free --record-durations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(evals): planner/executor/report modes — the CI re-platform surface

One PLANNER computes diff selection + the slice plan ONCE and writes a
manifest (--emit-plan <path> --slices K); K executors consume it
(--plan <path> --slice i), never self-selecting, and write slice-result
artifacts; a REPORT reconciles results against the manifest (--report
<dir>) fail-closed: a slice whose artifact never landed is a FAILURE,
a planned shard nobody reported fails, wrong-slice/duplicate/cross-tier
results fail. Kills per-slice selector divergence and hollow-lane
aggregation at the root.

- hollow-shard guard: under EVALS_ALL, exit 0 with ZERO executed tests
  (bun's 'Ran N tests' now captured by the classifier — additive) is
  'passed-empty' and fails the run; selective runs keep it 'passed'
  with one warning (in-file diff/tier self-skips are legitimate there);
  unknown counts are never guessed hollow
- retry parity: --retry 1 default + RETRY_OVERRIDES literals for the
  three files whose old matrix rows earned retries: 2 (stale entries
  pinned against disk)
- live smoke: gate plan = 48 shards across 6 slices; report mode exits
  1 on a fabricated missing slice, 0 when complete

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(ci): sliced paid lane (planner -> 6 executors -> fail-closed report)

The parity-phase re-platform: evals.yml gains a second, sliced lane
driven by scripts/test-paid-shards.ts — the SAME engine local
eval:bg:gate uses, so CI and local share one selection engine.

- plan-slices: ONE planner (fetch-depth 0 — the only job needing
  history) emits the manifest; selection fails open to run-all, never
  per-slice (the divergence class is structurally dead)
- eval-slices: 6-way matrix consuming the manifest; PTY seed +
  skill-registration steps run unconditionally (idempotent — a sliced
  lane cannot key them on suite names); aggregate spawn budget
  6 x EVALS_JOBS=2 x EVALS_CONCURRENCY=2 = 24 lane-wide (the matrix's
  40-way per row queued session startup behind 39 siblings — the
  timeout-flake family root); slice results + spooled shard logs
  uploaded as artifacts
- slices-report: reconciles slice artifacts against the manifest
  FAIL-CLOSED via --report — a slice whose artifact never landed, or a
  planned shard nobody reported, is a failure, not an absence
- sequenced needs: evals so provider concurrency never doubles while
  both lanes coexist; the matrix + its ratchets are deleted after
  demonstrated parity (intersection + expected-additions comparison)
- workflow_dispatch gains evals_all (default true) for parity runs and
  post-merge smokes — a dispatch can never silently select zero

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(ci): weekly periodic lane runs EVERY periodic test + gate census backstop

evals-periodic.yml re-platforms onto the sharded runner: planner
manifest → 6 executor slices → FAIL-CLOSED report. This IS the coverage
contract: all ~70 periodic-tier files weekly (EVALS_ALL=1), killing the
silent-rot class where a hard-coded 9-file matrix left ~57 files
running NOWHERE (the autoplan E2E rotted invisibly for months).

- test/helpers/periodic-exclude-data.ts: reasoned exclusions in their
  OWN literals file (deliberately not touchfiles-data — map-diff
  evaluates old versions of that file standalone). Every entry carries
  reason + tracking with a re-entry condition; the runner surfaces each
  exclusion per run; policy test pins real-file + non-empty fields.
  Initial: ship-idempotency + brain-privacy-gate (documented-red,
  never green) and skill-e2e-ios (manual hardware). The TODOS 'sidebar
  E2E trio' turned out already deleted — only tombstone tests remain.
- gate-census job: weekly EVALS_ALL gate-tier run — PR lanes are
  diff-billed, so without this the full gate census might never execute
  anywhere; with the hollow-shard guard it is a census-health check
  (exit 0 + zero executed tests fails), not just a test run.
- failure notification is a concrete gh issue UPSERT (one tracking
  issue, commented per red week — never issue-per-week spam), with
  issues:write scoped to the report job.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: TESTING_INTERNALS covers the 2026-08 runner overhaul

LPT-packed free suite + --record-durations, the emptied TREE_MUTATING
mechanism, the sharded paid runner as the single selection engine,
CI planner/executor/report with the fail-closed report and hollow-shard
guard, the weekly coverage contract + exclusions policy, and the
eval-budgets timeout tiers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(CLAUDE.md): testing prose matches the overhauled runners

- bun run test: duration-packed shards + --record-durations; the
  trailing serial tree-mutating shard no longer exists
- two-tier system: the sliced CI lanes (one engine local+CI), the
  weekly all-periodic coverage contract + exclusions, the gate census
- periodic detach timeout 32400 → 37800

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(TODOS): close the absorbed test-infra items, file the overhaul follow-ups

Closed with receipts: the periodic coverage contract (implemented as
full weekly coverage + exclusions), the eval-harness observability P1
(verified already landed: heartbeat, incremental _partial persistence,
live stderr + eval-watch), and the sidebar trio (already deleted —
tombstones remain). Filed: matrix deletion after parity, the
required-check maintainer decision, browse /tmp-namespace hardening,
PTY boot-readiness waits, the single typed test registry, bun-native
LPT swap, runBin/free-runner migrations, eval-list partial exclusion,
phantom key cleanup, duration-weighted slicing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* v1.73.0.0: test/CI overhaul — green means green, suites restructured for speed

Version + release notes for the audit-and-overhaul branch: every
silently-skipping or never-running test class fixed and tripwired, the
free suite duration-packed with the serial mutator shard dissolved, the
paid lane re-platformed onto the sharded runner (planner/slices/
fail-closed report, parity phase), the weekly all-periodic coverage
contract, eval-budget timeout tiers, and 95 new coverage tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): first-live-run fixes — executor history + two environment-blind assertions

The sliced lane's first run (PR #2721) did its job: the planner and
report worked, the manifest governed, and every failure had a name.
Three were fixable on the spot:

- executor + gate-census checkouts get fetch-depth: 0 — files with
  SELF-derived selection (the LLM-judge map, routing) walk git at
  module load, and selection is deliberately fail-closed on git errors,
  so the shallow checkout crashed those shards ('ambiguous argument
  main...HEAD'). The manifest still governs WHICH shards run.
- landscape --toc gate: the exact toBe(3) landscape-page count was
  font-metric-dependent (3 on Amazon Linux, 2 on ubuntu CI — the same
  disease the file's own page-index comment warns about). Now a
  comparative invariant: --toc must not CHANGE the landscape count vs
  a baseline render.
- paid-run-manifest parse test builds its manifest under EVALS_ALL so
  it never walks git (proven with GIT_DIR=/nonexistent).

Remaining first-run failures are newly-exposed rot in gate files that
had never executed in CI (skillify D1 refusal, session-intelligence
context-restore, one tpa-apple-ban retry flake) — being probed
separately; they are the lane WORKING, not the lane failing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(TODOS): file the three first-execution findings from the sliced lane's live run

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* v1.74.0.0: queue-advance — #2722 claims the v1.73.0.0 slot

The version gate caught a live queue collision (its whole job); same
MINOR bump level, next free slot per bin/gstack-next-version.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): per-shard CHROMIUM_PROFILE — the collision class duration packing exposed

Nine test files launch in-process persistent contexts or daemons that
default to the SHARED ~/.gstack/chromium-profile. Two concurrent shard
processes on one profile dir kill each other's browser — observed live
on CI once duration packing recomposed shards: handoff's
launchPersistentContext died 'Target page, context or browser has been
closed' (--user-data-dir=~/.gstack/chromium-profile in the call log)
while a sibling shard's daemon logged 'Chromium process crashed'. Hash
sharding had masked the collision by chance placement; handoff passes
standalone everywhere.

Fix at the runner, not per file: each shard child gets
CHROMIUM_PROFILE=<shard-state>/chromium-profile (the documented env
knob, same isolation idea as the existing per-shard TMPDIR). Files
within a shard run serially, so sharing the per-shard profile is safe;
config.test's resolution-order tests save/restore the env around their
assertions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): landscape --toc gate asserts promotion PRESENCE, not counts

Two rounds of CI receipts: the exact toBe(3) was font-metric-coupled
(3 on Amazon Linux, 2 on ubuntu), and the baseline-comparison repair
then failed 2-vs-3 across renders SECONDS apart in one CI job while the
sibling no-toc test saw 3 — per-render image-promotion timing makes any
count assertion here a coin flip. The sibling test owns exact promotion
counts; this test's actual invariant is that --toc does not break the
promotion machinery: >=1 landscape page + the TOC rendered. Also drops
the second render (halves the test's runtime).

Flaky per-render image promotion itself is worth its own look — noted
in TODOS with these receipts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(TODOS): file the per-render image-promotion nondeterminism (receipts from PR #2721)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): per-FILE Chromium profiles for the nine in-process launcher files

Completes the profile-isolation work: the per-shard CHROMIUM_PROFILE
stopped cross-shard kills; these nine files launch in-process
persistent contexts and could still collide with a lingering daemon a
sibling file spawned on the SAME shard profile. Each now scopes a
mkdtemp profile via beforeAll/afterAll (the module-scope-tripwire-safe
pattern), cleaned up per file. All nine green solo and in combined
runs, except the pre-existing commands+snapshot pairing — proven
identical WITH and WITHOUT these edits (baseline receipts) — which is
the daemon-lifecycle follow-up now extended in TODOS with this
session's receipts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(browse): Chromium-crash exit is daemon-only — embedded launches never kill their host

handleChromiumDisconnect unconditionally process.exit()ed. Correct for
the standalone daemon (its supervisor/user must notice); suicidal when
a TEST launches BrowserManager in-process: a mid-suite Chromium death
exited the whole bun shard with no terminal summary — the exact
truncation class the strict runner flags (observed live: CI shard 1 on
eb233299 died at cache-concurrent-refresh right after a daemon-spawning
gate test; with this fix the same pairing runs to completion and
REPORTS instead of dying).

The standalone entrypoint opts in via markDaemonProcess() under
server.ts's import.meta.main gate — the same embedder contract its
signal handlers already use (gbrowser phoenix keeps its own handlers).
Embedded contexts now get the disconnect log line and continue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): context-restore assertion is evidence-based, not prose-matching

The test failed twice per run in TWO CI cycles while passing locally
4/4: the prompt said 'present the content' and the check grepped the
FINAL message for exact phrases — local runs quoted the file, CI runs
paraphrased ('the most recent context is from branch-b...') and the
substring check lost the coin flip.

- prompt now demands machine-checkable output: the newest file's
  '## Working on:' heading VERBATIM + a literal 'RESTORED: <filename>'
  marker (the mtime-scramble and cross-branch subject matter untouched)
- assertion ordered strongest-first: RESTORED marker → legacy content
  phrases → tool-call corroboration (Read/Bash input naming the newer
  file, credited ONLY when the older file was never read — a
  both-files run must still present the right one)
- the older-file negative got STRONGER: an explicit RESTORED marker
  naming the older file fails even if wintermute words appear elsewhere
- sibling scan: context-recovery-artifacts got the additive prompt-side
  treatment only (quote the matched literals verbatim); its lenient
  1-of-6 assertion deliberately unchanged

3/3 consecutive local green with all evidence classes firing
(marker=true, content=true, toolNewer=true, toolOlder=false).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): skillify family — HOME==cwd broke project-skill registration

Root cause (forensically pinned from stream-json init events + a
kill-after-init probe): with HOME set EQUAL to the child's cwd, claude
resolves <cwd>/.claude/skills as the PERSONAL skills directory and the
seeded project-tier skills never register — the Skill tool returned
'Unknown skill'. The provenance-refusal test then improvised a refusal
whose wording missed the regex (the deterministic CI+local red); the
happy-path and approval-reject siblings passed only because their
agents self-recovered by Reading SKILL.md manually — silently not
exercising the Skill-tool path at all.

All three tests now use HOME=<workDir>/home (a fresh subdir keeps the
override's intent: child ~/.gstack writes land in the assertable
sandbox, without the cwd collision). Refusal test additionally: a
'not registered/unknown skill' tripwire (a not-loaded skill can never
pass as a refusal) and the refusal regex now matches assistant text
only — the skill BODY echoed into the transcript contains the exact
refusal message, so the old full-surface match could pass vacuously
once the skill loaded. Sibling disk assertions sweep both $HOME/.gstack
and cwd .gstack roots (positives and negatives).

Verified paid: refusal 2x consecutive green with the skill's EXACT
message rendered ('Launching skill: skillify' in-transcript), then the
full file 5/5 green (~$1.35) with both siblings driving real Skill
calls (25-27 turns each).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(TODOS): two of three first-execution findings fixed (skillify family, context-restore)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): context-restore gets a private home — the REAL root cause was fixture sharing

The evidence-based assertion fix was treating a symptom. The slice
artifact's embedded transcript showed the CI agent restoring
20260829-context-save-skill-test.md — the checkpoint the SIBLING
context-save test wrote into the SHARED gstackHome checkpoints dir,
which by filename-prefix ordering genuinely IS the newest. The agent
behaved CORRECTLY; the test's fixture set was open to concurrent
sibling writes, and bun --concurrent ordering differs between CI (save
finished first) and local (restore listed first) — the entire
local-green/CI-red split explained.

The restore test now uses its own .gstack-restore-home (the whole home
moves, not just the handed path — an agent deriving the dir from
GSTACK_HOME/projects/<slug> must land in the closed set too). Full file
4/4 paid green with all evidence flags firing.

Also: the on-failure shard-log artifact glob uploaded nothing — the
Fix-bun-temp step points TMPDIR at /home/runner/.cache, so the spool
lands there, not /tmp. Both eval workflows now glob both locations
(this gap is why diagnosing THIS failure required digging transcripts
out of the slice-results artifact).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(evidence): carry the real index mtime onto gstack-wtree's temp copy

The stat-cache seed (cp of the real index) stamped the temp index "now",
which defeats git's racy-git protection: an entry is only re-hashed when
its cached mtime is not older than the index file itself, so a same-size
rewrite landing in the same second as the last real index write looked
non-racy, kept its stale stat-cache entry, and vanished from the
fingerprint — evidence stayed FRESH after a source change. This is the
CI flake in test/evidence.test.ts "allow-paths carve-out" (sub-second
alignment on fast runners: expected STALE exit 1, got FRESH exit 0).

touch -r restores the original index timestamp, reinstating the exact
racy window git itself uses. Deterministic regression pin in
test/review-log.test.ts reproduces the miss with pinned zero-nsec
timestamps (fails on the old script, passes now); receipts: manual
probe shows the fresh-stamped copy returning the clean tree for a
same-size 'hello'→'howdy' rewrite while the mtime-carried copy detects
it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test): landscape gate bounds the promotion count instead of pinning 3

The alt-hinted image promotion rides the per-render measurement race
already filed in TODOS (2-vs-3 landscape pages on renders seconds
apart — CI receipts from PR #2721, now reproduced locally). Pin the
two deterministic promotions as the floor and the three promotable
blocks as the ceiling (anything above 3 means the veto leaked); the
veto/portrait assertions remain exact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Test <test@test.com>
2026-08-29 09:06:54 -07:00