Skip to content

v1.67.0.0 fix: the tracker wave — XProtect self-heal, complete installs, brain-sync integrity, 31 community PRs credited - #2604

Merged
garrytan merged 127 commits into
mainfrom
garrytan/fix-wave-issues-prs
Aug 17, 2026
Merged

v1.67.0.0 fix: the tracker wave — XProtect self-heal, complete installs, brain-sync integrity, 31 community PRs credited#2604
garrytan merged 127 commits into
mainfrom
garrytan/fix-wave-issues-prs

Conversation

@garrytan

Copy link
Copy Markdown
Owner

Summary

Full-tracker fix wave: 3 P0s, ~30 verified P1 fixes, ~25 community PR absorptions with credit, and the post-wave review-army + dual adversarial hardening. ~118 commits, 250+ files, +13K/−2.3K. Every fixed class carries a tripwire or regression test.

The three P0s

Browse & daemon lifecycle#2219 iron rule (healthy daemon survives browse start; busy → actionable refusal + --force-restart; regression-tested), #2254 stop short-circuit, #2551 upgrade defers to busy, #2220 signal policy + SIGHUP handler + tripwire, #2314 fixed port range — now genuinely below the macOS ephemeral floor (49151 cap; the initial 60000 cap left ~22% of picks inside the pool) with a bind-retry on the probe-then-bind race, #1952 Windows liveness via signal-0, windowsHide sweep + census, daemon crash log with token/content hygiene (F6 needle test), #2557 dead security-shield removal (−272 net lines, docs synced same commit, live L4 path pinned), first patchedDependencies (playwright-core windowsHide, #2160/#1989). Post-review: stop --force-restart kills a live daemon directly instead of booting a fresh one, daemon log rotates at 10MB, honest probe budget.

Install & setup — alias copy-then-rewrite (#2511/#2201), Windows re-run refresh (#2444), uninstall provenance gates (#2563, inventory + AUTO-GENERATED banner; anything else listed, never deleted), cursor full slice (#2547), settings-hook dedup (#2382), :user renders via --out-dir (#2569) + legacy-dirt migration, redact pre-push consent (opt-in stays, fail-open gaps closed, #1946), timeline Stop hook (fail-open, #2553), ios-qa DEBUG guard + bridge ordering (#2585/#2397), office-hours in codex/factory/opencode roots (#2449). Post-review: Windows refresh bypass provenance-gated (no longer deletes a user's own skill dirs), a failed brain-aware render can no longer vanish the installed skill set (tmp+atomic-swap), Windows copies prune gitignored build output, migrations see the real install dir.

Memory & gbrain — Windows slug + decisions allowlist (#2561/#2396), arithmetic-injection guard (#2588), bash routing on Windows (#2510) + gbrain-exec seam everywhere (#2471), --full full walk (#2406), honest brain-cache "missing" (#2587), memory-ingest rollout shapes (#2105/#2104), engine-locked whitelisted (#2456), bearer-token thin clients (#2520), GBRAIN_HOME segment (#2521), project-scoped MCP (#2499), source pins (#2417), offline --dry-run (#2536), dream classifier anchor (#2341), bun PATH hint (#2487).

Version tooling / diff-scope / redact — decision-11 end state: VERSION stays 4-digit truth, package.json carries the npm-valid translation (#2501), lockfiles synced only when present (#2568), subdir manifests (#2531), git version allocator (#2545), pinned repos compare base and current against the same file (#2462), diff-scope globs + exit codes (#2526/#2455/#2299), merge-base redact scan + parcel IDs (#2592/#2591), rebase/force-push scan proof (#2573). Post-review: version-path/package-json-path pins cannot escape the repo (.., absolute, symlink — all refused), env.kv stops flagging cacheKey-style names, prepush exclusion scoped to the push remote.

Templates/Codex/slug — under-Codex detection + nested-specialist skip (#2519), web-search flag behind one resolver constant across 19 sites (#2525), slug hygiene in every path position (#2550/#1851), review-army loops until clean (#2391), codex model probe (#2477) — post-review: negative-cached 15 min + bash-native watchdog deadline on stock macOS, question-registry absolute path (#2489), AGENTS.md routing (#2500), empty-find fallthrough (#2483), cygpath MSYS (#2452).

Quick wins/deps — retro glob (#2552), capability-check temp file (#2503), make-pdf boolean flags incl. --strict/--confidential with a source-derived guard (#2514), repo-mode stat order (#2195), hover note (#2445), .gitattributes LF (#2527), ios-qa preflight docs (#2581), adm-zip CVE override (#2485), puppeteer-core REMOVED (zero consumers), transformers/marked/socks bumps behind a green ONNX sidecar smoke, GitHub Actions bumps (#2594).

Test/generator infra — host-config golden isolation (#2532), hermetic-wiring tripwire + YAML ellipsis (#2586/#2589), prepush PATH separator (#2544), duplicate-preamble throw, 256KB Stop-hook tail read, timeline repair counts started vs completed per key.

Test Coverage

Step-7 coverage audit (5 subsystem agents over the full branch diff): ~945 / 1,119 changed paths pinned (~84%) — lib/ 87%, bin/setup/hooks 94%, browse/extension 81%, design/make-pdf/ios-qa 71%, scripts/hosts/workflows 68% (workflow YAML is the thin tail). Every residual gap is filed in TODOS.md ("v1.67 coverage-audit test-gap backlog") with a ranked list. Free suite at HEAD: all 7 shards green via the strict runner (~7,700 tests).

Pre-Landing Review

Review army (6 specialists: testing, maintainability, security, performance, data-migration, design-lite) + two independent adversarial passes:

  • Codex adversarial: 11 findings — both P1s fixed in-branch (brain-sync exclusive-author gate; version-bump path containment), P2s fixed (port range, codex-probe watchdog, lock release) or filed with rationale (enqueue lock window, iOS tap routing).
  • Claude adversarial: 17 findings — all four high-impact fixed in-branch (render-dir atomic swap, Windows setup provenance, lock trap placement, force-restart stop), mediums fixed (stop-hook counting, log rotation, xprotect homedir + retry wrap, negative caching) or filed (pair-agent consent, machine-global MCP scan, slug walk-up parity, next-version fallback breadth).
    All review-army findings triaged: fixed in one of 11 review-fix commits or filed in TODOS.md.

Eval Results

Gate tier via the sharded paid runner: 43/43. The initial run was 41/43; both reds were root-caused (never claimed pre-existing), fixed, and re-proven green at HEAD: ios-swift-build fixture parity 23/23 after the #2585 template sync; cso diff-mode passed at 226s under an honest 360s budget (transcript showed the agent completing the report and dying in closing telemetry at the old 240s cap). T6 exit (OV7): the affected-skill LLM-judge evals ran inside the gate tier post-template-changes with no judge regressions; expected prompt shifts are the #2525 web-search token unification and #2550 slug sanitization.

Scope Drift

Scope Check: CLEAN — plan-completion audit found ~16 non-plan commits, all sanctioned keep-green/review-driven work in plan-touched areas.

Plan Completion

PASS — 78/79 plan commits shipped against their final specs (every respec landed with commit-body receipts; F4–F13, E1–E5, ENG-OV1–11 all probed with concrete evidence). The one remaining item, c40/#2590, was reclassified close-only with a receipt: its target files were already repaired or deleted on main; the three survivors run 61 pass / 21 deliberate skips / 0 fail at HEAD.

Known flags (accepted or pre-existing)

  • skill:check reports claude/SKILL.md — generated file missing — pre-existing bug: skill:check fails because claude/SKILL.md is not generated #2256, unrelated to this wave.
  • XProtect classifier's timeout+<launched> fallback can heal a merely-slow launch once (~2 min cost, one-shot bounded) — accepted by design; a false positive self-limits.
  • Cursor auto-detect breadth (-d ~/.cursor triggers a full render for anyone who ever launched the IDE) — filed for a maintainer decision (TODOS).
  • iOS tap routing across overlay windows — filed; needs device testing.
  • slop:diff net +89/−83 — additions are empty catches in test cleanup paths, the repo's accepted best-effort pattern.

TODOS

  • Marked shipped: the four deferred waves from the 2026-08-14 tracker audit (browse-daemon lifecycle, install integrity, gbrain trust boundary, ship/version allocator) — landed in this wave; four residuals re-filed individually.
  • New sections filed: v1.67 fix-wave deferrals (next-wave queue), adversarial-review residuals (verified, with rationale), coverage-audit test-gap backlog (ranked), review-fix-batch deferrals.

Absorbed community PRs (credited in commits + CHANGELOG)

absorbs #2586, #2589, #2588, #2587 by @sneakygriff · #2561 by @source-utsho · #2510, #2406 by @ShahriarLak · #2544 by @luckywenapere · #2547 by @szsunyuan · #2431 by @gregario · #2585 by @Bastea · #2397 by @IDSTUK · #2409 by @SomSamantray · #2414 by @SYKhayyat · #2461 by @phuttimatebenchanakatkul · #2450 by @vaston-viji · #2557, #2559 by @frederik-kaster-noygear · #2419 by @meshailabs · #2542 by @guyua9 · #2417 by @exGeni · #2540, #2531, #2545 by @CarringtonCreative · #2500 by @gamerey43 · #2483 by @tranthanhnhatkhoa · #2452 by @chiragborse1 · #2501 by @YiftahR · #2568 by @ortonom · #2592, #2591 by @Two-Six-Alpha-1115 · #2527 by @mlaniak · #2581 by @itstimwhite · #2485 by @anupamme · #1851 by @harjothkhara · #2594 by @dependabot

Post-merge maintainer checklist (manual — nothing auto-closes except the Fixes roster)

Fixes roster (auto-closes on merge)

Fixes #2554. Fixes #2317. Fixes #2454. Fixes #2549. Fixes #2532. Fixes #2396. Fixes #2471. Fixes #2105. Fixes #2511. Fixes #2201. Fixes #2444. Fixes #2563. Fixes #2382. Fixes #2569. Fixes #1946. Fixes #2553. Fixes #2250. Fixes #2220. Fixes #1952. Fixes #2219. Fixes #2254. Fixes #2551. Fixes #2314. Fixes #2456. Fixes #2520. Fixes #2521. Fixes #2499. Fixes #2536. Fixes #2525. Fixes #2489. Fixes #2550. Fixes #2391. Fixes #2477. Fixes #2519. Fixes #2552. Fixes #2503. Fixes #2341. Fixes #2514. Fixes #2195. Fixes #2487. Fixes #2449. Fixes #2445. Fixes #2462. Fixes #2526. Fixes #2455. Fixes #2299. Fixes #2160. Fixes #1989. Fixes #2573.

Documentation

Post-ship doc sync (/document-release, commit 8cc379b8): all 6 core docs cross-referenced against the 121-commit diff, plus an independent doc review (Claude subagent; Codex timed out) whose 12 verified gaps were fixed.

Doc diff preview

  • BROWSER.md: daemon port range corrected to 10000-49151 in 4 places (new port-allocator.ts stays below the macOS ephemeral pool); daemon lifecycle gains the XProtect launch-kill self-heal (runs in the daemon, once per process, manual guidance on failed heal) and a new busy-vs-dead item (bounded ~8s health probe, never kills an alive pid, --force-restart escape hatch); stop on a dead daemon documented as success; browse-daemon.log documented; source map gains port-allocator.ts, xprotect-heal.ts, and the security-sidecar pair.
  • ARCHITECTURE.md: port-selection section updated (range + allocator rationale); CLI liveness rewritten for signal-0 + bounded probe + --force-restart; dead security surface (L4b Haiku transcript classifier, DeBERTa ensemble, canary injector, deberta model cache) marked removed per CLAUDE.md's do-not-redocument directive.
  • README.md: prompt-injection paragraph corrected for fix(browse): remove the dead security shield and its unfed /health status #2557 (shield icon, Haiku vote, GSTACK_SECURITY_ENSEMBLE all removed as live claims); manual uninstall fixed (rm -rf for asset-bearing skill dirs, alias-copy removal by name, cursor + opencode roots, Stop-hook cleanup note); the default-on gstack-timeline-stop Stop hook documented with its removal command.
  • CONTRIBUTING.md: setup now documents runtime-asset links and alias rewritten-copies; gstack-config gbrain-refresh render path corrected to the Global-git installs stay permanently dirty: the claude :user render happens in-place, so every upgrade leaves a stash #2569 out-dir + gstack-relink flow.
  • CLAUDE.md: new note that package.json carries the npm-valid 3-digit translation of the 4-digit VERSION (do not "fix" the mismatch); patches/ + lib/version-source.ts added to the project tree; prefix-setting section updated for runtime assets and alias copies; workflow count corrected to 14.
  • CHANGELOG.md (v1.67.0.0 entry, wording only — no entries removed): two release-summary em dashes replaced per the voice rules; "-535 lines" corrected to the verifiable "-272 net lines"; "v1.65-style upgrade migration" corrected to the upgrade-time quarantine-clear in setup.

Documentation Debt

  • README "Sidebar agent" paragraph + ARCHITECTURE prompt-injection intro still describe the pre-rip chat-path sidebar agent (natural-language queue, Sonnet/Opus routing). Pre-existing narrative staleness — needs a human-judgment rewrite around the Terminal-pane architecture; suggest a follow-up /document-generate pass.
  • XProtect self-heal has reference + explanation coverage but no tutorial — acceptable since the heal is fully automatic.
  • (The port-allocator.ts:118 stale comment the doc pass flagged is already fixed on this branch.)

Test plan

  • Free suite: all 7 shards green via the strict runner (~7,700 tests), evidence-recorded via gstack-evidence
  • Gate evals 43/43 (initial 41/43; both reds root-caused, fixed, re-proven green at HEAD)
  • Regen idempotence across all 10 hosts; skill:check fresh (modulo pre-existing bug: skill:check fails because claude/SKILL.md is not generated #2256)
  • Redact scan on PR body + CHANGELOG bytes at sink

🤖 Generated with Claude Code

garrytan and others added 30 commits August 16, 2026 08:33
Fixes #2532. The codex/factory golden tests read gitignored artifacts that
only gen-skill-docs.test.ts (serial tree-mutating phase) produces, so the
file failed in isolation and on clean clones (the #2536 "3 failures then 0"
symptom). beforeAll now generates a host's artifacts iff its ship SKILL.md
is missing — never overwriting existing ones, so stale artifacts still fail
the golden. The file is also classified TREE_MUTATING so its provisioning
runs in the serial window, not racing parallel readers.

Verified: full pass with .agents/ and .factory/ deleted (74/74 in isolation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…~/.claude ban

The skill-seeding tripwire asserted every seeded symlink target must NOT
start with ~/.claude — but on the default global-git install the repo
itself lives at ~/.claude/skills/gstack, so every CORRECT symlink (which
must resolve into the live repo tree, as the very next assertion requires)
carried the banned prefix. The test could never pass on a default install:
pristine v1.64.1.0 (c118e24) fails it in any worktree under
~/.claude/skills/ and passes elsewhere (verified 2026-08-15).

Exempt targets that realpath into the resolved repo ROOT before applying
the operatorClaude ban — realpath both sides so a symlinked HOME can't
dodge the tripwire. Genuine escapes (a target under ~/.claude but outside
the repo) still fail with the escape message.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…strict parser breaks on bare ellipsis)

A bare ... inside a plain YAML scalar is a document-end marker that strict
YAML parsers (Bun.YAML among them) reject mid-scalar. catalog-trim truncation
appends '...' to any description whose lead exceeds 200 chars, so any
truncated description would generate a SKILL.md with unparseable frontmatter.
Add the ellipsis test to toYamlInlineScalar's needsQuote so such scalars are
emitted double-quoted, plus unit coverage for the quoting rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hardens the #2508/#2362 class: a second {{PREAMBLE}} occurrence — even a
prose mention, which is exactly how spec/SKILL.md.tmpl re-expanded the full
~12K-token preamble mid-document — now fails generation with the template
path instead of silently shipping a doubled preamble. Pure exported guard
(assertSinglePreamble) called from resolvePlaceholders, unit-tested with the
original prose-mention shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Discovered while landing the duplicate-{{PREAMBLE}} guard: importing
scripts/gen-skill-docs.ts executes its top-level body, which regenerates the
entire claude host (71 GENERATED files) at import time. catalog-trim.test.ts
does that import from a PARALLEL shard — the same read-during-regeneration
hazard class as #2532, invisible only because the regen is byte-identical on
a fresh tree. Move it to the serial tree-mutating window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`test/redact-prepush-hook.test.ts` shadows `git` with a stub by prepending a
temp dir to PATH, built as `${stubDir}:${process.env.PATH}`. On Windows the
separator is `;`, so that produces one unparseable entry, the stub is never
found, and the REAL git runs — the diff succeeds, `gitStrict` never throws, and
the hook exits 0 where the test expects 1. It fails as a wrong assertion rather
than as a portability problem, which is what made it hard to place.

Replace it with a `prependPath` helper mirroring the one already in
test/gstack-brain-context-load.test.ts, which handles both platform details:
`path.delimiter`, and a case-insensitive lookup of the existing env key —
Windows commonly spells it `Path`, and adding a second `PATH` alongside an
inherited `Path` leaves the winner up to the spawn implementation.

On POSIX the helper resolves to `{ PATH: binDir + ":" + process.env.PATH }`,
byte-identical to the expression it replaces, so behaviour there is unchanged.

Fixing the separator alone does not make the test pass on Windows, and it
cannot: the premise is that a signal-killed child yields `spawnSync`
status === null, and Windows has no equivalent (a force-killed process reports
a non-zero exit code). The stub is also a `#!/bin/sh` file named `git`, which
Windows will not execute, since process creation resolves through PATHEXT and
ignores the shebang. A Windows variant would assert the non-zero-exit branch
instead — a different branch than the test name claims — so the test is gated
with test.skipIf(process.platform === "win32"), matching
test/session-runner-timeout.test.ts and test/setup-emoji-font.test.ts.

Windows before: 14 pass, 1 fail. After: 14 pass, 1 skip, 0 fail (3 consecutive
runs). Unchanged on POSIX, where it should still run and pass — worth
confirming in CI, since I can only verify the Windows half here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gstack-decision-log enqueues projects/<slug>/decisions.jsonl after every write,
but none of the 16 managed globs matched it, so compute_paths_to_stage rejected
every one at its "must match at least one allowlist glob" check.

The writer and the syncer disagreed silently: enabling artifacts sync backed up
learnings, plans, designs and timelines -- everything except the durable decision
ledger -- and nothing reported a miss, because a dropped path prints exactly what
a synced one does when the queue is otherwise empty.

Add the three decisions.* globs and class them artifact so they also sync in
artifacts-only mode.

The test reads the heredocs out of the script rather than executing it:
gstack-artifacts-init.test.ts drives the real script through #!/bin/bash shims and
a colon-separated PATH, so it cannot run on Windows -- the platform where the
companion slug bug bit.
…ot spawn

bin/gstack-slug is a `#!/usr/bin/env bash` script with no file extension. Windows
honors neither the shebang nor PATHEXT for an explicit path, so spawnSync fails
ENOENT and resolveSlug returned its literal fallback, "unknown".

Every decision on the machine was therefore filed under
~/.gstack/projects/unknown/ -- one bucket shared by every project -- while the
bash-side Context Recovery preamble resolved the real slug, found no
decisions.active.json there, and skipped through a bare `if [ -f ... ]` with no
else.

Nothing failed. Both decision bins (log and search) missed identically, so writes
and searches stayed consistent with each other, and the only component that
resolved correctly was silent by design. Measured on one machine: 62 decisions
accumulated over 10 days and 170 skill runs, surfaced zero times.

shell:true is not the fix here, unlike #1731 -- cmd.exe cannot run a bash script
either. Nor is re-spawning through `bash`: on Windows that frequently resolves to
WSL, whose $HOME and /mnt/c paths yield a different slug AND a different cache
directory, trading one split store for another.

Instead, port gstack-slug's own three steps (cache -> git remote -> basename),
keeping its alphabet and its MSYS-form cache key so both paths agree. The
fallback is win32-gated, so POSIX behaviour is byte-identical.

Tests exercise the fallback on every platform (only the gating is win32-specific),
so POSIX CI catches a regression that would otherwise surface only on a Windows
user's disk, plus a static gate pinning the platform check.
…st-pull; sanitize _GBRAIN_HOST

Re-derived from PR #2588 under the generated-file screening rule (resolver
hunks taken; SKILL.md files regenerated, not accepted). A poisoned
.brain-last-pull could reach bash arithmetic ($(( ))) — a code-execution
vector from a writable state file; the timestamp is now validated numeric
before use. _GBRAIN_HOST from ~/.claude.json is clamped to hostname-safe
characters before echo. Ship goldens refreshed to the regenerated output.

Co-authored-by: sneakygriff <89592870+sneakygriff@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The brain-sync stage failed on EVERY Windows run with "is not
recognized as an internal or external command", so /sync-gbrain always
reported ERR brain-sync among otherwise green stages.

#1731 gave these spawns shell: NEEDS_SHELL_ON_WINDOWS. That is correct
for the gbrain.cmd shim and does nothing here: shell:true routes through
cmd.exe, which resolves .cmd/.bat via PATHEXT but has no concept of a
shebang, so an extension-less bash script is rejected outright. A .cmd
shim needs a shell; a shebang script needs an interpreter. The two cases
look identical and are not.

The failure was quiet rather than loud. artifacts_sync_mode defaults to
pushing curated artifacts to git, so a Windows user's learnings piled up
uncommitted in ~/.gstack indefinitely while the sync report showed one
red line out of four.

New bashScriptInvocation() resolves Git for Windows' bash explicitly and
passes the script as argv[0]. It prefers Git bash over a bare `bash` on
PATH because WindowsApps ships a bash.exe that is the WSL launcher, which
would read C:\... as a Linux path; GSTACK_BASH overrides for unusual
installs; forward slashes because bash treats backslashes as escapes; and
it returns null when no bash exists so the stage says so plainly instead
of surfacing an unactionable spawn error.

The #1731 tripwire asserted the shape that does not work, so it now
asserts the opposite (never a raw spawnSync(brainSyncPath, ...)) and six
unit tests cover the resolver.

Verified on Windows: the stage now reports "OK brain-sync curated
artifacts pushed (4.2s)" and the artifacts repo committed + pushed on its
own. Affected-test set unchanged at 14 pre-existing failures before and
after, with 6 new passing tests.
Fixes #2471. With shell:true on Windows, node/bun join argv into one cmd.exe
string without quoting, so a repo path with a space — the default
C:\Users\First Last\ layout — split into two arguments and every gbrain call
carrying a path silently targeted the wrong location (worst: `sources add
--path`). All gbrain CLI invocations now build their (cmd, argv, shell)
triple through gbrainInvocation(), which quotes risky arguments for cmd.exe's
re-parse (embedded quotes doubled). The four direct spawn sites in
lib/gbrain-sources.ts route through the seam; the #1731 static invariant is
upgraded for seamed files (any direct "gbrain" opener is the violation) and
kept as-is for lib/gbrain-local-status.ts. POSIX behavior unchanged
(shell:false, passthrough argv).

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

Fixes #2549 (P0 data loss). Every drain exit previously truncated the WHOLE
queue (six `: > "$QUEUE"` sites), which (a) destroyed privacy/mode-held
entries while misattributing them as "no allowlisted changes", (b) destroyed
entries enqueued concurrently during the drain, and (c) left push-failed
commits stranded locally with nothing ever re-pushing them until unrelated
new work arrived.

Now: compute_paths_to_stage classifies every entry (stageable / retained
privacy-held / dropped skipped-invalid-unmatched-missing); rewrite_queue
re-reads the LIVE queue at mv time and removes only this drain's processed
paths (retained + concurrent appends + unparseable lines survive; atomic
tmp+mv); an unpushed-commit detector at run start re-pushes stranded local
commits (receipted fail-closed; a receipt refusal skips the retry rather
than wedging the drain; guards missing origin/<branch>; runs inside the
existing lock). Status lines carry counts; full drop paths go to a 0600
sidecar (.brain-sync-drops.json) so filenames stay out of transcripts.
--drop-queue remains the one intentional truncation.

Matrix added: privacy retention, unmatched/missing counted drops + sidecar
mode, unparseable-line preservation, surgical same-drain retention, push-fail
commit retention + detector re-delivery on an EMPTY queue, receipt-refusal
skip. 35/35 in test/brain-sync.test.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`runCodeImport()` walked with a bare `gbrain sync --strategy code --source X`.
The strategy is right, but that walk is incremental: it only revisits files
changed since the source's checkpoint. A file missed at the ORIGINAL import is
therefore never revisited and stays out of the index indefinitely.

The reindex-code pass below cannot rescue it. It re-chunks pages that already
exist and never walks the filesystem — the same property the comment directly
above already relies on when explaining why the walk has to run first. That fix
landed one flag short: it made a fresh source get pages at all, but left
`--full` unable to discover a file the first walk skipped.

Net effect: `/sync-gbrain --full` did not perform a full walk, and re-running it
never re-detected the gap.

The failure is silent, which is what makes it expensive. Nothing errors, nothing
warns, and the verdict block still reports OK while `gbrain search` and
`gbrain code-def` answer out of a partial index. It reads as "gbrain is weak at
code questions" rather than "the index is incomplete".

Measured on two local code sources before and after this change, counting
exported functions resolvable via `gbrain code-def`: one went from 61/201 (30%)
to 180/201 (89%), importing 79 files that had no page at all; the other had
whole source files missing entirely and reached 93%. Both had been serving
search from a partial index for weeks.

Scoped to `--full` so incremental runs stay fast. `--yes` because this spawns
non-interactively and a full walk otherwise prompts to confirm import cost.

Anyone can check their own brain without applying this:

    gbrain sync --source <id> --strategy code --full --dry-run

and compare "N file(s) would be imported" against that source's page_count.
Worth knowing while doing so: the default strategy is markdown and --strategy
is per-invocation, never persisted on the source, so dropping the flag reports
strategy=markdown and a handful of files.
…s on gbrain failure

A gbrain-unreachable failure in fetchRecentDecisions and fetchSalience
used to be converted into a cached 'successful' empty digest ("_No prior
skill runs recorded._" / "_No salient pages in last 14d._") that
refreshEntity stamped with last_refresh. The false negative then
survived every subsequent TTL cycle, indistinguishable from a genuine
zero-rows result. Now failure returns null, so cmdGet's existing
missing/stale-fallback machinery reports the true state — matching what
fetchGoals and fetchSimplePage already do on failure.

Also adds an Array.isArray guard in fetchRecentDecisions so a malformed
payload ({pages: {}} etc.) classifies as failure instead of crashing
refreshEntity mid-refresh; a genuinely empty pages array still renders
the honest empty digest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rebuild path refreshes every per-project entity against the real gbrain
CLI; with an unreachable brain each spawn runs to its own timeout, and under
machine load the stack exceeds bun's 5s default (observed 5.2-5.4s,
identically on pre-#2587 binaries — a load flake, not a regression). 30s
budget matches the sibling brain-sync suite's convention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes #2105. Codex rollout JSONL moved to
{ type: 'response_item', payload: { type: 'message', role, content: [...] } };
the parser's legacy payload.message branch never fired on it, so every Codex
session imported as an empty shell (message_count: 0 — 243/243 sessions on
the reporting machine). Both shapes now parse; non-message response_items
(reasoning etc.) are ignored. parseTranscriptJsonl exported for direct unit
tests (CLI path unchanged — import.meta.main guard).

Note: #2104's staging-in-gitignored-tree half is already defended on main
(--include-gitignored + GIT_CEILING_DIRECTORIES, #2144, plus the #2486
reconcile guard) — verified, no change needed; it moves to the close-only
roster.

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

The #2588 absorb refreshed all three ship goldens, but `bun run
gen:skill-docs` regenerates the CLAUDE host only — the codex/factory goldens
were copied from artifacts rendered before the resolver change and failed
against a fresh external-host regen in the serial test phase. Re-rendered
with --host codex / --host factory and re-copied.

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

Fixes #2514. The parser treated any non-flag token after a flag as its value,
so `$P generate --toc essay.md` ate essay.md as --toc's value and failed with
"missing input" — the skill's own documented usage only worked when two
boolean flags happened to be adjacent. BOOLEAN_FLAGS enumerates the no-value
flags; value flags (--watermark, --to, --title, ...) are unchanged. main()
now runs behind import.meta.main so tests import the parser directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes #2195. On GNU coreutils `stat -f` SUCCEEDS (filesystem status, not a
format string), so the BSD-first fallback chain never fell over — it fed
multi-word filesystem output into the cache-age arithmetic and crashed under
set -u on Windows Git Bash. GNU `stat -c` fails cleanly on BSD/macOS, making
GNU-first deterministic on both; the mtime is numeric-validated before
arithmetic as a last line of defense.

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

Fixes #2552's live half. The gbrain context-query glob targeted
~/.gstack/projects/<slug>/retros/*.md — a directory and extension nothing
writes — so prior-retro recall was dead on every brain-aware run. /retro
saves to .context/retros/*.json (repo-local); the query now reads that. The
issue's second defect (quoted-tilde orphan sweep) is already fixed on main —
the preamble sweeps with "$HOME/..." — verified, no change needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ser's repo

Fixes #2503. On worktree-pinned brains `gbrain put` materializes the checked
page as _capability_check_<pid>.md in the current directory (the user's
repo), and `gbrain delete` removes the page but not the file — every
/sync-gbrain run left a stray file in the repo root. The check now deletes
the materialized file explicitly after the page delete.

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

Fixes #2445. Both behaviors are by design but produced confidently wrong
verification output: hovering a below-the-fold element scrolls the page
before a "rest state" screenshot (exit 0, wrong section), and the daemon's
tab survives sessions so a bare `reload` can act on whatever earlier work
left open. The screenshot-evidence section now names both traps with the
concrete guards (assert window.scrollY; always goto before verifying).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
.gitattributes pins LF for every other text format in the repo (*.md,
*.tmpl, *.yml, *.yaml, *.json, *.toml, *.sh, *.ts, extensionless scripts,
even the hash-pinned diagram-render dist files). *.txt is the one text
format left unpinned.

On Windows with core.autocrlf=true, that means the two tracked .txt files
are rewritten to CRLF at checkout and then read as permanently modified:

  gstack/llms.txt                                   +174 bytes
  make-pdf/test/fixtures/combined-gate.expected.txt  +20 bytes

git status is never clean, and /gstack-upgrade's 'git stash' step saves a
phantom stash on every upgrade — one that pops back to an empty diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On a fresh Claude install, link_claude_skill_dirs installed only SKILL.md
(+ sections/) per skill. Every skill that reads a sibling runtime file at
.claude/skills/<name>/<file> was broken out of the box: /review stopped at
'Read .claude/skills/review/checklist.md' (file never installed), and qa's
templates/references, plan-devex-review's dx-hall-of-fame.md,
gstack-upgrade's migrations/, and careful/freeze's bin/ hooks were all
silently missing. Codex/Factory/OpenCode/Kiro installers already copied
these; the primary host never did.

Fix: a shared _link_skill_runtime_assets helper installs EVERYTHING a skill
ships next to its SKILL.md, with an explicit exclusion list (F7):
node_modules, dist, test, *.tmpl, hidden files. Exclusion-list polarity
means a newly added asset installs by default instead of being silently
dropped. Assets refresh unconditionally on re-run (rm + relink/copy), so
Windows real-dir copies pick up changes after git pull.

New free test runs the real installer functions against the live repo into
a temp skills dir with a TWO-CLASS referenced-paths assertion (ENG-OV7):
alias-relative refs (.claude/skills/<name>/<path>) must exist under the
install; repo-anchored refs (~/.claude/skills/gstack/<path>) must exist in
the tree modulo an explicit built-artifact allowlist (browse/design/
make-pdf dist + the compiled gstack-global-discover). Known-broken class-2
refs (#2250 bare bin names) are ratcheted: the test fails if they quietly
start existing without the entry being removed.

Fixes #2317
Fixes #2454

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two back-compat alias dirs — _gstack-command (root router) and
connect-chrome (→ open-gstack-browser) — symlinked the canonical SKILL.md
verbatim, so each alias re-served the canonical frontmatter name:. Claude
Code keys skills on that name and requires global uniqueness: the
connect-chrome duplicate silently shadowed /open-gstack-browser (whichever
readdir returned first won), and the _gstack-command duplicate could drop
the ENTIRE personal-skills set — every /gstack command vanished until the
user hand-deleted the alias dirs, and the next setup re-broke it.

Fix: copy-then-rewrite. A shared _install_alias_skill_md helper reads the
SOURCE SKILL.md and writes a fresh copy with name: rewritten to the alias
dir's own name (_gstack-command / connect-chrome / gstack-connect-chrome).
sed never edits in place: on Unix the old install was a symlink into the
repo, and an in-place rewrite through it would have corrupted the generated
source (eng review E2). bin/gstack-relink gets the same treatment for its
root-alias helper, and its discovery loop now skips symlinked source dirs
so the connect-chrome repo symlink can't re-mint the duplicate.

Tests assert: installed aliases are NOT symlinks, carry their own unique
names, all installed frontmatter names are globally unique, re-runs refresh
cleanly, legacy symlinked aliases are replaced not written through, and the
source files stay byte-intact.

Fixes #2511
Fixes #2201

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y/opencode hosts

On Windows (Git Bash / MSYS2, no Developer Mode), _link_or_copy installs
REAL directory copies. The install guards in link_codex_skill_dirs,
link_factory_skill_dirs, link_opencode_skill_dirs, and create_agents_sidecar
only ran the copy when the target was a symlink or missing — true on the
first install, never again. Every subsequent ./setup after a git pull
reported 'gstack ready (codex).' and exited 0 while silently refreshing
nothing: users ran stale SKILL.md forever. (link_claude_skill_dirs already
handled this; the other hosts never got the treatment.)

Fix: all five guard sites bypass the symlink-or-missing check when
IS_WINDOWS=1 — _link_or_copy rm -rf's the destination first, so the real-dir
copy refreshes in place. Unix behavior is unchanged (symlinks still pass the
guard via -L and serve updates without re-copying).

The new bash-fixture test drives the REAL extracted functions through the
install → upstream change → re-run cycle under IS_WINDOWS=1 (v1 must become
v2), pins the sidecar-skip behavior, checks the Unix path stayed a symlink,
and statically asserts the bypass at all five sites so factory/opencode
can't regress. Registered in the Windows-safe curated list
(KNOWN_WINDOWS_SAFE) so it actually runs on the windows-latest CI lane —
the 'bin/' pattern hit is a fixture path segment, not a shebang spawn.

Fixes #2444

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

On Windows, setup installs skills as REAL directory copies (cp -R via
_link_or_copy). gstack-uninstall's per-skill loop filtered on [ -L ], so
every copy was skipped: --force exited 0 and printed 'gstack uninstalled.'
while leaving ~52 gstack-* directories plus _gstack-command/ behind in
~/.claude/skills. The same filter also missed the standard Unix shape (real
dir + symlinked SKILL.md), which was left as a dangling-symlink husk.

Fix: the loop now handles all three install shapes. Symlink entries keep
the existing readlink check. Real dirs with a SYMLINKED SKILL.md are removed
when the link points into gstack (same semantics as setup's cleanup
helpers). Real dirs with a REAL-FILE SKILL.md — the Windows copy shape — are
removed ONLY when both provenance gates pass (F8): (a) the directory name is
in gstack's skill inventory (source dir names, frontmatter names, gstack-
prefixed variants, and the alias dirs), and (b) the SKILL.md carries the
existing generated banner '<!-- AUTO-GENERATED from' (ENG-OV10: every
pre-v1.67 copy already carries it; a NEW marker would refuse to delete
legitimate old installs, recreating the bug). Anything failing a gate is
listed to stderr and never deleted — a user's own skill that happens to
share a name with a gstack skill survives.

Tests: a fake-tree fixture covers removed/kept/listed for every shape
(including the F8 name-collision row), and a census test asserts every
installable skill's generated SKILL.md carries the banner so the gate can't
strand a bannerless skill. Registered in the Windows-safe curated list —
the copy shape is exactly what windows-latest exercises.

Fixes #2563

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
'./setup --host cursor' was accepted by the flag parser and then did
nothing: no INSTALL_CURSOR branch existed, so the script built binaries,
printed no 'ready' line, and installed zero skills — Cursor users had no
way to install gstack at all.

Full install slice, re-derived from PR #2547 by @szsunyuan onto the
current installers: generate .cursor/ skill docs (host config already
existed), create a minimal ~/.cursor/skills/gstack runtime root (root
SKILL.md + bin/lib/browse assets + review checklist pair + ETHOS.md +
supabase config — bin and lib travel together because bin scripts import
../lib), link the generated gstack-* skills, and plant the repo-local
.cursor/skills/gstack sidecar WITHOUT ever wiping the generated SKILL.md
files it shares a directory with (link-before-sidecar ordering keeps the
generation fallback alive). Auto mode detects Cursor via the cursor
binary or the ~/.cursor footprint. gstack-uninstall removes
~/.cursor/skills/gstack* and per-project .cursor/skills/gstack* — and
never rmdir's .cursor itself, where Cursor stores user rules.

Re-derivation deltas from the PR: the link guards carry the #2444
IS_WINDOWS bypass (re-runs refresh real-dir copies), lib/ and
supabase/config.sh ride along like every other runtime root, and the
hosts/cursor.ts sidecar field is omitted (HostConfig no longer carries
one — sidecar behavior lives in setup).

Fixes #1358

Co-authored-by: Yuan Sun <forrest.sun527@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes #2382.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…stalls stay git-clean

On a global-git install with gbrain, ./setup and 'gstack-config
gbrain-refresh' ran gen:skill-docs:user IN PLACE inside the install
checkout, rewriting ~16 TRACKED SKILL.md files. The checkout stayed
permanently dirty, every /gstack-upgrade 'git stash' saved a redundant
snapshot of generated content, and the growing stash list invited a 'git
stash pop' that would lay stale instruction markdown from an older gstack
over the current version — a quiet wrong-rules failure mode.

Fix, wired through machinery that already existed (gen-skill-docs
--out-dir + the symlink install layer): brain-aware SKILL.md now renders
into the untracked ~/.gstack/render/claude, and both Claude installers
serve the render when present — setup's link_claude_skill_dirs prefers
$GSTACK_HOME/render/claude/<skill>/SKILL.md, and bin/gstack-relink does
the same so a later config change can't silently flip skills back to the
blockless canonical source. setup wipes and rebuilds the render each run,
repoints installed skills after a successful render, and removes a stale
render (re-linking canonical) when gbrain is gone. gbrain-refresh renders
to the out-dir and repoints via relink; its 'this dirties the install's
git tree' caveat is retired because it no longer does.

A one-time upgrade migration (gstack-upgrade/migrations/v1.67.0.0.sh, F12)
restores the legacy dirt: unstaged modifications to SKILL.md / sections/
*.md files in the install checkout are git-checkout'd back to canonical;
anything outside that footprint (user edits, untracked files, staged work)
is left alone and reported. Idempotent, non-fatal, symlinked installs
skipped.

Tests: render-preference behavior for both installers, static pins that
every executable :user invocation carries --out-dir and the caveat text is
gone, migration fixture (restore/leave/idempotent/no-op matrix), and the
existing out-dir render test now asserts 'git status --porcelain' gains
zero new entries across a full :user render.

Fixes #2569

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: playwright-core processLauncher launches chrome-headless-shell without windowsHide — console window per browser launch on the node path (follow-up to #2151, node_modules twin of #1989) memory-ingest renders all Codex rollout sessions as empty bodies (parser expects payload.message; current format is payload.type=='message' + payload.content) Windows: residual browse console flash from vendored playwright-core processLauncher taskkill (missing windowsHide) — distinct from #1784 terminal-agent watchdog flashes a console window every tick on Windows — isProcessAlive() spawns tasklist instead of signal-0 gstack-redact: pre-push guard is opt-in (installed by nothing), fails open on git errors, and misses common token types (glpat-, Bearer, hf_, npm_, GCP JSON)

10 participants