diff --git a/bin/gstack-slug b/bin/gstack-slug index e9b2aaf02..924454722 100755 --- a/bin/gstack-slug +++ b/bin/gstack-slug @@ -27,7 +27,11 @@ # injection when consumed via source or eval. set -euo pipefail -CACHE_DIR="$HOME/.gstack/slug-cache" +# GSTACK_HOME-aware, matching lib/bin-context.ts's native port (#2561): the +# bash writer and the TS reader must key the SAME cache, and a test running +# with GSTACK_HOME= must write its cache junk there, not into the real +# home (observed: 2,528 stale temp-cwd entries accumulated in ~/.gstack). +CACHE_DIR="${GSTACK_HOME:-$HOME/.gstack}/slug-cache" PROJECT_DIR="$(pwd)" # Encode absolute path as cache key: /Users/j/foo → _Users_j_foo CACHE_KEY=$(printf '%s' "$PROJECT_DIR" | tr '/' '_') @@ -37,8 +41,14 @@ SLUG="" # 0. Explicit env override — wins over everything. Escape hatch for vendored # sub-repos and other genuine "subdir IS its own project" edge cases. +SLUG_FROM_ENV=0 if [[ -n "${GSTACK_PROJECT_SLUG:-}" ]]; then SLUG=$(printf '%s' "$GSTACK_PROJECT_SLUG" | tr -cd 'a-zA-Z0-9._-') + # Per-invocation escape hatch, never a durable identity: persisting it + # would rebind THIS cwd's slug for every later env-less run (observed: a + # test exporting GSTACK_PROJECT_SLUG from the repo root rebound the whole + # repo's session state to the test's slug). + SLUG_FROM_ENV=1 fi # 1. Walk up from pwd, tracking the OUTERMOST ancestor with a canonical @@ -160,7 +170,7 @@ SLUG="${SLUG:-$(basename "$PROJECT_DIR" | tr -cd 'a-zA-Z0-9._-')}" # injection, but the invariant should not depend on that reasoning). SLUG=$(printf '%s' "$SLUG" | tr -cd 'a-zA-Z0-9._-') -if [[ -n "$SLUG" ]]; then +if [[ -n "$SLUG" && "$SLUG_FROM_ENV" -eq 0 ]]; then CURRENT_CACHE="" if [[ -f "$CACHE_FILE" ]]; then CURRENT_CACHE=$(cat "$CACHE_FILE" 2>/dev/null || true) diff --git a/test/gstack-slug-sanitize.test.ts b/test/gstack-slug-sanitize.test.ts index 7c5dc8f7d..9933ca01e 100644 --- a/test/gstack-slug-sanitize.test.ts +++ b/test/gstack-slug-sanitize.test.ts @@ -63,3 +63,40 @@ describe('gstack-slug cache-read sanitization', () => { } }); }); + +// The GSTACK_PROJECT_SLUG escape hatch is per-invocation, never durable: a +// test exporting it from the repo root once rebound the ENTIRE repo's session +// state (evals, decisions, timelines) to the test's slug via the cwd cache. +// The cache is also GSTACK_HOME-aware now, matching lib/bin-context.ts's +// native port — temp-home runs must not litter the real ~/.gstack (observed: +// 2,528 stale temp-cwd entries). +describe('slug cache hygiene', () => { + test('an env-override run never writes the cwd cache', () => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), 'slug-env-')); + try { + const r = spawnSync(['bash', SLUG_BIN], { + cwd: os.tmpdir(), + env: { ...process.env, GSTACK_HOME: home, GSTACK_PROJECT_SLUG: 'override-slug' }, + }); + expect(r.stdout.toString()).toContain('SLUG=override-slug'); + expect(fs.existsSync(path.join(home, 'slug-cache'))).toBe(false); + } finally { + fs.rmSync(home, { recursive: true, force: true }); + } + }); + + test('an env-less run caches under GSTACK_HOME, not $HOME', () => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), 'slug-home-')); + try { + const r = spawnSync(['bash', SLUG_BIN], { + cwd: os.tmpdir(), + env: { ...process.env, GSTACK_HOME: home }, + }); + expect(r.exitCode).toBe(0); + const entries = fs.readdirSync(path.join(home, 'slug-cache')); + expect(entries.length).toBe(1); + } finally { + fs.rmSync(home, { recursive: true, force: true }); + } + }); +}); diff --git a/test/skill-e2e-cso.test.ts b/test/skill-e2e-cso.test.ts index 64aa18bde..9bd0ed380 100644 --- a/test/skill-e2e-cso.test.ts +++ b/test/skill-e2e-cso.test.ts @@ -161,9 +161,14 @@ IMPORTANT: - Focus on changes in the current branch vs main. - The webhook.ts file was added on this branch — it should be analyzed.`, workingDirectory: csoDiffDir, - maxTurns: 25, + maxTurns: 40, allowedTools: ['Bash', 'Read', 'Write', 'Edit', 'Grep', 'Glob', 'Agent'], - timeout: 240_000, + // 360s/40 turns: the v1.67 wave grew the audit session legitimately — + // transcript-verified, the agent finds the webhook vuln, spawns the + // verification subagent, and writes the report, then gets killed at + // ~215s in its CLOSING telemetry under the old 240s/25-turn budget. + // The full-audit sibling already runs at 300s. + timeout: 360_000, }); logCost('cso', result); @@ -176,7 +181,7 @@ IMPORTANT: ).toBe(true); recordE2E(evalCollector, 'cso-diff-mode', 'e2e-cso', result); - }, 240_000); + }, 400_000); }); describeIfSelected('CSO v2 — infra scope', ['cso-infra-scope'], () => {