v1.60.2.0 fix: free-suite drift on dev machines (eval-list cwd, gemini regex, observability floor) (#2470)

* fix(test): eval-list CLI spawns from neutral cwd so slug detection can't dodge the fixture store

getProjectEvalDir() probes cwd-relative .claude/skills/gstack/bin/gstack-slug;
with cwd=ROOT on a dev machine the self-symlink makes it succeed, routing reads
to an empty project-scoped dir instead of the seeded legacy ~/.gstack-dev/evals.
Neutral cwd + absolute script path fails both probes deterministically — same
behavior as CI.

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

* fix(test): case-insensitive remediation-hint match for reworded Gemini NOT-READY message

The message now leads with 'Export GEMINI_API_KEY...' (free-tier OAuth
deprecation); the old pattern only knew lowercase 'export'.

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

* fix(test): observability check 11 floor 6 -> 5 after shell-free spawn removed promptFile unlink

aa3bd6f0 deleted the prompt temp file (and its /* non-fatal */ marker) when it
dropped shell interpolation. The invariant — every runner I/O path wrapped
non-fatally — still holds at the 5 remaining sites, now named in the comment.

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

* docs(todos): P1 — free-suite exit code masked by in-process force-exits

Five browse test files setTimeout(() => process.exit(0), 500) inside the shared
bun process; the suite can exit 0 before the summary with real failures masked.
Receipts + fix path filed.

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

* chore(release): v1.60.2.0

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-07 15:08:12 -07:00
committed by GitHub
co-authored by Claude Fable 5
parent a3259400a3
commit 960c3a8d6c
7 changed files with 81 additions and 8 deletions
+1 -1
View File
@@ -133,7 +133,7 @@ describe('gstack-model-benchmark --dry-run', () => {
const notReadyLines = out.split('\n').filter(l => l.includes('NOT READY'));
expect(notReadyLines.length).toBeGreaterThanOrEqual(2);
for (const line of notReadyLines) {
expect(line).toMatch(/(install|Install|login|export|Run|Log in)/);
expect(line).toMatch(/(install|login|export|run|log in)/i);
}
} finally {
fs.rmSync(emptyHome, { recursive: true, force: true });
+8 -2
View File
@@ -51,8 +51,14 @@ function writeEvalRun(evalDir: string, filename: string, timestamp: string, turn
}
function runEvalList(...args: string[]): { stdout: string; stderr: string; status: number } {
const result = spawnSync('bun', ['run', 'scripts/eval-list.ts', ...args], {
cwd: ROOT,
// cwd is the temp HOME, NOT the repo root: getProjectEvalDir() probes the
// cwd-relative .claude/skills/gstack/bin/gstack-slug, and on dev machines
// with the self-symlink that probe succeeds, routing reads to an (empty)
// project-scoped dir instead of the legacy ~/.gstack-dev/evals this test
// seeds. A neutral cwd makes both slug probes fail deterministically, so
// the CLI always uses the seeded legacy dir — same behavior as CI.
const result = spawnSync('bun', ['run', path.join(ROOT, 'scripts', 'eval-list.ts'), ...args], {
cwd: tmpHome,
env: {
...process.env,
HOME: tmpHome,
+4 -3
View File
@@ -92,9 +92,10 @@ describe('session-runner observability', () => {
);
// Count non-fatal comments — should be present for each new I/O path
const nonFatalCount = (src.match(/\/\* non-fatal \*\//g) || []).length;
// Original had 2 (promptFile unlink + failure transcript), we added 4 more
// (runDir creation, progress.log, heartbeat, NDJSON append)
expect(nonFatalCount).toBeGreaterThanOrEqual(6);
// Five wrapped I/O sites: runDir creation, progress.log append, heartbeat
// write, per-test NDJSON append, failure-transcript write. (Was 6 until
// the shell-free spawn removed the promptFile unlink and its marker.)
expect(nonFatalCount).toBeGreaterThanOrEqual(5);
});
});