From 0542230e632b5e6b2d776aadbe21fb4b8109d0d5 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 2 Nov 2026 05:28:00 -0800 Subject: [PATCH] test(fixtures): leak scan skips Capy's git shim log in fixture homes (#3860) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- CHANGELOG.md | 8 ++++++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- package.json | 2 +- test/git-fixture-template.test.ts | 17 +++++++++++++++++ test/helpers/git-fixture-template.ts | 8 ++++++-- 6 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fc172340..f32c13f1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [1.92.80.0] - 2026-11-02 + +**On a Capy machine, the git fixture tests no longer fail locally because of Capy's git shim log.** Capy's `/usr/bin/git` shim appends to `$HOME/.capy/git-shim.log`, so each fixture home that ran git during its template build ended up with a log naming the template. The leak scan in `test/helpers/git-fixture-template.ts` flagged it, and 34 tests in `test/train-backstop.test.ts`, `test/restamp-mentions.test.ts` and `test/pregate-user-policy.test.ts` failed on every Capy lane, though they passed on the remote gate. + +### Tests +- `templateReferences` skips `.capy/git-shim.log`. Nothing reads that file back, and every other file is still scanned. +- `test/git-fixture-template.test.ts` plants the shim log in a template's `home/.capy/` (the copy succeeds), and plants `home/.capy/state.json` as a control (the copy still throws). + ## [1.92.79.0] - 2026-11-02 **/plan-tune no longer reports a mismatch between your stated and observed preferences when it has no observed answers behind it (#3436).** `--check-mismatch` compared a declared value against an inferred value that had never moved from its 0.5 starting point, so a strong declaration with no matching answers printed `scope_appetite: declared 0.9 vs inferred 0.5 (gap 0.4)` even though no logged answer touched that dimension. diff --git a/VERSION b/VERSION index dfb847afe..f17ba7e50 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.92.79.0 +1.92.80.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 192027e8e..835c2c05f 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.92.79.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.92.80.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/package.json b/package.json index b8a73016c..2da268dcb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.92.79", + "version": "1.92.80", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/test/git-fixture-template.test.ts b/test/git-fixture-template.test.ts index 76375466c..f63a6c002 100644 --- a/test/git-fixture-template.test.ts +++ b/test/git-fixture-template.test.ts @@ -71,6 +71,23 @@ describe('git fixture template', () => { expect(() => leaky.copy()).toThrow(/still references its template [^\n]*\n.*config/); }); + test("Capy's git shim log in a fixture home (home/.capy/git-shim.log) is not a leak; any other file naming the template still is", () => { + const plant = (rel: string) => gitFixtureTemplate({ prefix: 'gstack-fixture-template-shim-', build: (root) => { + const meta = build(root); + fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true }); + fs.writeFileSync(path.join(root, rel), '# capy git shim v2: ts cwd ppid parent verb\n2026-11-02T02:15:30Z\t' + path.join(root, 'work') + '\t1\tbun\tcat-file\n'); + return meta; + }, rebind: (copy, tpl) => rebindGitDir(path.join(copy, 'work', '.git'), tpl, copy) }); + const shim = plant(path.join('home', '.capy', 'git-shim.log')); + const other = plant(path.join('home', '.capy', 'state.json')); + try { + const c = shim.copy(); + roots.push(c.root); + expect(git(path.join(c.root, 'work'), 'remote', 'get-url', 'origin')).toBe(path.join(c.root, 'origin.git')); + expect(() => other.copy()).toThrow(/still references its template [^\n]*\n.*state\.json/); + } finally { shim.dispose(); other.dispose(); } + }); + test('native, forward-slash, backslash-escaped and Git Bash spellings are each rewritten, and each one left behind is found', () => { const to = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-fixture-spellings-')); roots.push(to); diff --git a/test/helpers/git-fixture-template.ts b/test/helpers/git-fixture-template.ts index a646747ab..5fb9f72eb 100644 --- a/test/helpers/git-fixture-template.ts +++ b/test/helpers/git-fixture-template.ts @@ -52,7 +52,11 @@ export function rebindGitDir(gitDir: string, from: string, to: string): void { } } -/** Files and symlinks under dir (and the template's meta value) that still name templateRoot; zlib-compressed object storage is skipped. */ +/** + * Files and symlinks under dir (and the template's meta value) that still name templateRoot. Skipped: zlib-compressed object + * storage, and the log Capy's /usr/bin/git shim appends to in whatever HOME it runs under (.capy/git-shim.log), which names + * the template whenever a fixture home ran git during build; nothing reads it back. + */ export function templateReferences(dir: string, templateRoot: string, meta?: unknown): string[] { const needles = pathSpellings(templateRoot).map(s => Buffer.from(s)); const names = (buf: Buffer) => needles.some(n => buf.includes(n)); @@ -62,7 +66,7 @@ export function templateReferences(dir: string, templateRoot: string, meta?: unk const p = path.join(d, entry.name); if (entry.isSymbolicLink()) { if (names(Buffer.from(fs.readlinkSync(p)))) hits.push(p); } else if (entry.isDirectory()) { if (!(path.basename(d) === 'objects' && /^(pack|[0-9a-f]{2})$/.test(entry.name))) walk(p); } - else if (entry.isFile() && names(fs.readFileSync(p))) hits.push(p); + else if (entry.isFile() && !(entry.name === 'git-shim.log' && path.basename(d) === '.capy') && names(fs.readFileSync(p))) hits.push(p); } }; walk(dir);