Conversation
…US 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>
…ailed 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>
…ges 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>
…nfig + 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>
…ions, 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>
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>
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>
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 8e7a03c superseded)
…eady 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 5797d32 superseded); +2 tests
`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.
`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>
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.
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)
Wave polish on the #2734 absorption: the 512-char scp-path lookahead window follows the UUID_CONTEXT_CHARS named-constant convention instead of a magic number at the slice site. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
…ne-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>
…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
…poppler
describeBinary reports version="unknown" flavor="unknown" for every poppler
install, so logDiagnostics prints nothing useful on the most common
implementation. Two independent causes:
1. poppler writes the -v banner to stderr and exits 0. execFileSync returns
stdout (empty) and does not throw on a zero exit, so the stderr fallback in
the catch block is unreachable. The in-code comment already notes poppler
exits 0, but only the throwing path reads stderr.
2. flavor is matched against the version line alone. poppler prints
"pdftotext version 26.06.0" on line 1 and names itself on line 2,
"Copyright ... The Poppler Developers", so even a working stderr read
yields "unknown".
Switch the probe to spawnSync, which returns both streams regardless of exit
status, match the version banner rather than assuming line 0, and derive the
flavor from the full output.
Measured on poppler 26.06.0 (Homebrew, macOS), same machine and binary:
before: { version: "unknown", flavor: "unknown" }
after: { version: "pdftotext version 26.06.0", flavor: "poppler" }
xpdf is unaffected: it exits non-zero and names itself on line 1, so it
resolved correctly before and still does.
Tests use shell shims reproducing each vendor's banner, stream and exit status,
since a real pdftotext cannot be assumed present in CI. Two of the four fail on
this commit's parent; the xpdf and no-banner cases pass there and are included
as regression guards rather than red-proofs.
…aemon 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
Wave polish on the #2690 absorption: the describe-scope mkdtemp left one directory per run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cli.ts resolved the Chromium profile dir with a hardcoded $HOME/.gstack/chromium-profile, while browser-manager launches the profile returned by config.resolveChromiumProfile(), which honours CHROMIUM_PROFILE and GSTACK_HOME. killOrphanChromium() and cleanChromiumProfileLocks() are called with no argument, so whenever CHROMIUM_PROFILE was set they cleaned locks for, and killed Chromium on, the DEFAULT profile rather than the one being launched. Starting a browser with a custom profile therefore evicted an unrelated browser running on the default profile. Delegating to resolveChromiumProfile() also picks up GSTACK_HOME and os.homedir(), so the cleanup path now matches the launch path on Windows where HOME is frequently unset.
…ill'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>
Wave-added coverage for the #2732 absorption: a 6-line fix with zero tests is how the hardcoded path shipped in the first place. resolveChromiumProfile's env behavior is already pinned in config.test.ts; this pins cli.ts's delegation and forbids the hardcoded path from returning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…val (#2727) Wave-amended: test moved to browse/test/ (browse unit-test convention); trailing-semicolon normalization kept — it is load-bearing for the expression wrapper
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
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)
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
Follow-through on the #2748 absorption: existing installs answered the continuous-checkpoint and model-overlay prompts with markers beside the install; v1.78 reads them from GSTACK_HOME. Copy them once so nobody gets re-prompted. Idempotent, non-fatal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The #2709 helper landed between two import statements; imports now stay contiguous. No behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Exit 124 reached the string-signature branch before the timeout fail-open, so a slow probe whose partial output happened to quote 'permission denied' classified as MODEL_UNUSABLE_INSTALL — a deterministic-broken verdict from a transient condition. 124 is now excluded from the signature branch, and the detect/display greps share one hoisted _BROKEN_SIG regex (they had already drifted: display dropped 'not executable'). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
challenge-mode initialized turn_completed_count but tested turn_failed via 'in dir()'; consult-mode initialized neither and rebuilt the counter with a dir() conditional per event. Both parsers now init turn_completed_count and turn_failed up front and use plain checks — same semantics, no module-globals introspection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
A leftover $INSTALL_DIR.bak from a crashed upgrade would make the mv nest the live install inside it, and the failure-restore arm would 'restore' the stale backup — possibly deleting the only good copy. The upgrade now refuses to start and tells the user to inspect/salvage the backup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
'refusing to send unscanned <noun>' read as if 'unscanned' modified a missing word for sink nouns like 'the spec body'; now 'refusing to send <noun> unscanned'. Generated spec section refreshed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--timeline-stop-hook=noo silently normalized to yes AND wrote yes to config — a persisted decision the user never made. Unrecognized values now warn (naming the source), apply the default for this run only, and skip the config write. The opt-out log line names the actual decision source (flag/env/config) and no longer claims a removal that may not have happened. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nt warning One shared _gitleaksWarned flag served two different messages: a 'machine under load, retrying next file' warning early in a run permanently silenced the later 'gitleaks not in PATH; secret scanning disabled' warning — the user never learned scanning was off for good. Split into per-message flags. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The killed/SIGTERM/ETIMEDOUT discrimination was hand-rolled at three sites (gbrain version probe, engine classifier, gitleaks probe) and free to drift; it now lives once in lib/gbrain-exec.ts. GbrainBinProbe is exported (it's the return type of exported probeGbrainBin) and the cache carries a rationale comment: caching a timeout for process lifetime is deliberate — the memo dedupes the ~3 probes of one short-lived preamble process. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…se docstring --out-dir and --link-root shared near-identical inline parsing; one helper now owns it. The rewriteSectionBase docstring said 'no-op when --out-dir is unset' but the gate is the link root (which --link-root can set independently) — it now describes the real behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…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>
…erage 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>
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>
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>
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>
…ault 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>
…er 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>
Pins the >145KB truncation regression (process.exit before the pipe drained): 900 MEDIUM findings -> 259KB JSON report through a sleep-first POSIX consumer that holds the 64KiB kernel buffer full at child exit; asserts complete parseable JSON with matching counts and exit 2, plus an --auto-redact mirror (700 redactions, final sentinel byte arrives). Harness proven red against a copy of the bin with process.exit restored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Companion to the gen-skill-docs parsePathFlag extraction: the pin still asserts the same invariant (LINK_ROOT defaults to OUT_DIR, so an in-place render stays a byte-exact no-op) against the new expression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stale-backup refusal + empty-scanned-body guard on the mktemp bullet, the redact pipe-truncation fix as its own item (a v1.77 bug), and test counts refreshed to the post-fix-batch suite (8,660). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A hardcoded /usr/bin:/bin child PATH breaks spawn('bash') on the Windows
curated lane (spawn resolves against the CHILD env's PATH; no bash.exe
lives there). Hermeticity is carried by HOME/GSTACK_* overrides, not PATH.
Found by the cycle-2 review pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cost Retrying a slow probe per FILE (#2715's slow!=absent split) re-paid up to probe+retry (12s default) per file — an 887-file ingest on a loaded box spent hours re-asking the same slow question. After 3 consecutive slow answers the run stops probing and warns once that remaining files go unscanned; the availability cache is still never written, so the next process probes fresh. Slow/absent discrimination is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e consult First-occurrence-keeps-bare was walk-order-dependent ACROSS runs: a source that got the suffixed slug once could take the bare slug the next run (its collider aged out or was skipped as unchanged), leaving gbrain holding the same transcript under two slugs — and a NEW collider could claim a bare slug that state shows belongs to an unchanged source, silently overwriting that page. disambiguateSlugs now consults state.sessions: a recorded slug stays owned by its source_path, re-ingested sources keep their slug verbatim, fresh assignments never take another source's slug, and legacy duplicate records (pre-#2724 overwrites) resolve first-owner-wins and self-heal on the next state write. Stateless behavior is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
README.md: the persistent timeline Stop hook opt-out (#2677) — flag, env var, and config key with resolution order. BROWSER.md: browse stop against a dead daemon now reaps the recorded headless Chromium child, identity-verified (#2709). CLAUDE.md + CONTRIBUTING.md: free-suite test count ~7,000 → ~8,700 (8,660 as of this wave). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CONTRIBUTING.md: the day-to-day example now edits the .tmpl (SKILL.md is generated); the OSV row states the explicit --config load and the reasoned, expiring ignore contract. BROWSER.md: stop row mentions the identity-checked Chromium reap; env table gains CHROMIUM_PROFILE and GSTACK_DISABLE_GPU rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"Both red weekly lanes are green again" overclaimed: OSV is verifiably green (pinned scanner, frozen install, branch dispatch), but the periodic lane keeps its pre-wave churn (#2756) — what this wave proves is that the v1.76 regression that silenced plan reviews is dead. Flagged by the cross-model doc review; headline now leads with the user-visible outcome. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The v1.78.0.0 fix wave — both red weekly CI lanes restored (periodic AUQ-collapse regression rooted in v1.76's inferable spawned trigger; OSV lane green from 105 advisories via a real dependency pass and a suppression file that actually loads), 18 community PRs absorbed with authorship preserved, 9 confirmed no-PR bugs fixed directly, led by the destructive #2679 upgrade-path guard.
Community PRs absorbed (18, authorship preserved on the branch commits; please keep the Co-authored-by trailers below in the squash message):
cross_project_learningsvalues, silently disabling cross-project learnings with no supported way to undo it #2673)awaitsilently returns empty string (block wrapper drops the expression value) #2727; wave: test relocated)origin, misreports fork branches as already cleaned up #2696; wave: gh command corrected — nameWithOwner is empty on gh 2.83; fork branches report-only)New fixes (11):
mktempfailure silently disables the redaction pass and can delete a live install #2679)no-cli, bypassing thetimeoutwhitelist from #1964 — a bun-shim install on POSIX silently loses every brain-aware block #2716)Test Coverage
Tests: 8,519 → 8,660 (+141). Every behavior fix carries a regression test verified red on a scratch worktree of v1.77.0.0 via base-compatible reproducers (compile-failure receipts don't count). New structural guards: generated-doc fence pairing (with CommonMark opener-length tracking), PIPESTATUS portability (executed under real bash+zsh), the codex JSONL parser executed under real python3, OSV config wiring with mandatory ignoreUntil, redact CLI empty-path, cli.ts profile-dir delegation, browse stop-reap wiring pins, and the v1.78.0.0 migration's first dedicated suite. The redact pipe-drain test was proven red against a copy of the bin with the old exit pattern restored (65,536-byte truncation reproduced).
Pre-Landing Review
/plan-ceo-review (HOLD SCOPE, CLEAR, 8 findings absorbed) + /plan-eng-review (CLEAR, 3 findings absorbed) + two Codex outside-voice passes (31 points total; 22 absorbed — peelable-first commit order, ordered-SHA picks, base-compatible receipts, start-time PID identity, explicit OSV --config, ignoreUntil expiry, bash+zsh execution tests; rejections recorded with rationale in the plan).
Ship-time review army (7 specialists + red team, 92% coverage audit, 2 cycles): three CRITICALs found and fixed (the darwin reap identity probe was inert — /proc read on a platform without /proc; a chained-IIFE-plus-statement eval misclassification; a launch-path crash where ps is missing), all 8 coverage-audit gaps closed as tests, and cycle 2's single finding (a Windows-lane PATH hazard in a new test) fixed. Three judgment calls went to the maintainer: gitleaks probe cooldown (implemented), cross-run slug persistence (implemented), land-and-deploy anonymous-HTTPS ownership edge (deferred to #2765).
Eval Results
FINAL: all 20 PR checks green including all six paid gate slices and the fail-closed slices-report (three consecutive rounds). OSV verified green both locally (pinned v2.3.8, frozen clean installs, exit 0) and via workflow_dispatch on the branch. The v1.76 periodic AUQ collapse is dead across three pinned-container periodic dispatches on this branch (deterministic reviewCount=0 never recurred; round-3 passes every test that drove rounds 1-2). The periodic lane itself remains churn-red as it was BEFORE the wave — it has never been green in its v1.77 shape; residual classification with receipts (pre-existing exited/hits=[] class reproduced on clean v1.77, band-edge variance, known flakes) is in #2756. Free suite green (8,660 tests under xvfb; the earlier sandbox handoff failures were rooted to a missing Xvfb package plus a real product gap — runtime handoff never engages the Xvfb auto-spawn — filed as #2764). The ship-time fix batch re-runs the gate lanes on this push.
Documentation
Post-ship doc sync (
/document-release, commits bd4a721 + 3e3c723):./setup --no-timeline-stop-hook, theGSTACK_TIMELINE_STOP_HOOKenv var, and thetimeline_stop_hookconfig key (flag > env > config; an explicit "no" removes a live registration and the choice survives upgrades).browse stopagainst a dead daemon also reaps the headless Chromium child recorded in the state file, verified by start time + a Chromium-looking cmdline before any signal (browse: headless GPU process pegs ~800% CPU on macOS 26 / M5; --disable-gpu alone insufficient #2709); thestopcommand row says so too; the environment table gainsCHROMIUM_PROFILEandGSTACK_DISABLE_GPUrows.review/SKILL.md.tmpl(SKILL.md is generated) and regenerates; the OSV scanner row documents the explicit--configload (the filename is not auto-discovered) and the reasoned, expiring ignore contract.bun run gen:skill-docsre-run produced zero drift: SKILL.md files, llms.txt (55 skills / 76 browse commands), and the agents digest are all in sync with templates at HEAD.gstack-upgrade/migrations/v1.78.0.0.shfollows CONTRIBUTING's migration format.Documentation Debt
gen-skill-docs --link-root— reference in the CHANGELOG contributors section and the inline docblock only; the render-into-tmp-then-swap contract isn't in docs/TESTING_INTERNALS.md.chromiumPid/chromiumStartTimefields (reap behavior itself is now documented; field-level reference would live in the state-file docs).Flagged for maintainer (review findings, deliberately not edited)
No architecture diagram drift: ARCHITECTURE.md's diagram entities all survive this wave unrenamed.
TODOS
Residuals filed in TODOS.md ("v1.78 fix-wave deferrals"). Disposition drafts for the deferred/rejected PRs (#2707, #2704, #2750, #2749, #2670, #2695, #2729) are in .context/wave-dispositions/ pending maintainer review — nothing posted to contributors yet. Two OSV tracking issues to file on merge (referenced by the ignore ledger).
Closes
Fixes #2679
Fixes #2727
Fixes #2728
Fixes #2742
Fixes #2696
Fixes #2694
Fixes #2673
Fixes #2724
Fixes #2685
Fixes #2716
Fixes #2738
Fixes #2692
Fixes #2677
Fixes #2671
Fixes #2669
Fixes #2735
Fixes #2687
Refs #2756 (periodic-lane residual ledger — pre-existing churn, classified with receipts)
Refs #2709 (flags + reap shipped; macOS live verification tracked in TODOS.md)
Refs #2695 (superseded by the overrides fix; parallel diagnosis credited)
Refs #2765 (land-and-deploy anonymous-HTTPS ownership edge — deferred at ship review, filed with repro shape)
🤖 Generated with Claude Code
Co-authored-by: y$un_ forrest.sun527@gmail.com
Co-authored-by: Lockyer 135391289+Lockyer228@users.noreply.github.com
Co-authored-by: Udhdhav kheni udhavkheni12@gmail.com
Co-authored-by: schienbiz 274676847+schienbiz@users.noreply.github.com
Co-authored-by: David Park show@davidani.com
Co-authored-by: alopes50 alex@alexlopes.com
Co-authored-by: Peter van Leeuwen petervanleeuwen@SB-petervanleeuwen.local
Co-authored-by: Denis Zjukow denis.zjukow@gmail.com
Co-authored-by: Paul Snyman 5826275+snymanpaul@users.noreply.github.com
Co-authored-by: Adam Badar badaradam10@gmail.com
Co-authored-by: loulanyue 260355617@qq.com
Co-authored-by: Shreshth Kapoor shreshth@osiflow.com
Co-authored-by: Ryan Ayers rayers@dividia.net
Co-authored-by: Simon Altit simon.altit@gmail.com
Co-authored-by: ptt 1928627998@qq.com
Open workspace in Conductor