Files
gstack/test/empty-find-fallthrough.test.ts
T
Garry TanandClaude Fable 5 4da5be24a6 fix(test): scope rendered-output tripwires to repo sources; stop cdp-e2e's env leak
Two hermeticity holes surfaced by the wave's final gate. (1) The three T6
tripwires (branch-slug, codex-flag, empty-find) enumerated the whole tree
including the workspace-local .claude/ install, which is not generated
output and can carry dangling symlinks from unrelated sessions — one ENOENT
there failed all three. They now scan repo sources only. (2)
browse/test/cdp-e2e.test.ts mutated process.env.GSTACK_HOME at module scope
without restore; in one-process shard runs that leaks into every later test
file — observed baking cdp-e2e's temp render path into artifacts that
outlived it (53 dangling SKILL.md symlinks in a workspace install). The
original value is now restored in afterAll. The exact test that performed
the polluted relink remains unattributed; both known leak vectors are
closed and the workspace was repaired via an explicit gstack-relink.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 11:19:22 -07:00

96 lines
3.7 KiB
TypeScript

/**
* Empty `find | xargs ls -t` must not fall through to cwd (#2483).
*
* GNU xargs runs `ls -t` once even on EMPTY input, and `ls -t` with no
* operands lists the CURRENT DIRECTORY — so on a fresh install (no
* ceo-plans / checkpoints / plans yet) a random cwd .md becomes "the plan"
* or "the latest checkpoint". `xargs -r` pins the BSD skip-on-empty
* behavior on both GNU and BSD (same shape as the landed
* bin/gstack-codex-session-import fix, #2482).
*
* Re-derived from community PR #2483 by @tranthanhnhatkhoa.
*/
import { describe, test, expect } from 'bun:test';
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { HOST_PATHS } from '../scripts/resolvers/types';
import type { TemplateContext } from '../scripts/resolvers/types';
import { generateContextRecovery } from '../scripts/resolvers/preamble/generate-context-recovery';
const ROOT = path.join(import.meta.dir, '..');
function makeCtx(): TemplateContext {
return {
skillName: 'test-skill',
tmplPath: 'test.tmpl',
host: 'claude',
paths: HOST_PATHS.claude,
preambleTier: 2,
};
}
describe('empty find must not fall through to cwd (#2483)', () => {
test('no resolver emits a bare `xargs ls -t` (must be `xargs -r ls -t`)', () => {
const out = execSync(
`grep -rn "xargs ls -t" "${path.join(ROOT, 'scripts')}" "${path.join(ROOT, 'bin')}" || true`,
{ encoding: 'utf-8' },
);
expect(out.trim()).toBe('');
});
test('rendered Context Recovery uses the guarded form at both find sites', () => {
const rendered = generateContextRecovery(makeCtx());
const bareSites = rendered.split('xargs ls -t').length - 1;
expect(bareSites).toBe(0);
const guardedSites = rendered.split('xargs -r ls -t').length - 1;
expect(guardedSites).toBe(2);
});
test('live block: empty checkpoints dir yields NO checkpoint, not a cwd file', () => {
const rendered = generateContextRecovery(makeCtx());
const m = rendered.match(/_LATEST_CP=\$\(.*\)/);
expect(m).not.toBeNull();
const line = m![0];
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-home-'));
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-cwd-'));
try {
// Fresh install shape: the checkpoints dir exists but is EMPTY,
// and the cwd holds a decoy markdown file.
const proj = path.join(home, 'projects', 'unknown');
fs.mkdirSync(path.join(proj, 'checkpoints'), { recursive: true });
fs.writeFileSync(path.join(cwd, 'DECOY.md'), '# not a checkpoint\n');
const script = `_PROJ="${proj}"\n${line.replace(/\$\{_PROJ\}|"\$_PROJ"/g, '"$_PROJ"')}\necho "LATEST_CP=[$_LATEST_CP]"`;
const out = execSync(`bash -c '${script.replace(/'/g, `'\\''`)}'`, {
cwd,
encoding: 'utf-8',
});
expect(out).toContain('LATEST_CP=[]');
expect(out).not.toContain('DECOY.md');
} finally {
fs.rmSync(home, { recursive: true, force: true });
fs.rmSync(cwd, { recursive: true, force: true });
}
});
test('no generated SKILL.md carries the unguarded form', () => {
const out = execSync(
`grep -rln "xargs ls -t" --include=SKILL.md "${ROOT}" || true`,
{ encoding: 'utf-8' },
);
// node_modules and vendored trees are not generated output; nothing in
// the repo's generated skills may carry the unguarded form.
const hits = out
.split('\n')
.filter(Boolean)
.filter((f) => !f.includes('node_modules'))
// The workspace-local .claude/ install is not generated output and can
// carry dangling symlinks from unrelated sessions.
.filter((f) => !f.includes('/.claude/'));
expect(hits).toEqual([]);
});
});