* test: reproduce false GSD SDK ready signals on Linux (#3231)
* fix(install): require persistent SDK reachability before reporting ready (#3231)
* changeset: pr=3249 for #3231
* fix(install): filter _npx from login-shell PATH probe (CR finding 1)
Apply filterNpxFromPath() to the getUserShellPath() result before passing
it to isGsdSdkOnPath(), mirroring the same filtering already applied to
process.env.PATH. Without this, a transient _npx entry in the login-shell
PATH can falsely satisfy the cross-shell reachability check and reintroduce
the false-ready condition this PR fixes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): unconditional legacy-shim replacement assertion (CR finding 2)
Replace readFileSync+includes source-grep check with isLegacyGsdSdkShim()
and add an else branch asserting that when sdkReady is false, a warning/error
was emitted. Previously the sdkReady===false path had no assertion at all,
allowing the test to pass without verifying any postcondition.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: replace text-grep assertions with structured ones (CR finding 2 + nitpick)
Finding 2: restructure the legacy-shim replacement assertion to branch on
isLegacyGsdSdkShim() state (a behavioral fact) rather than console output,
and add an unconditional postcondition for both branches.
Nitpick 3 (4 locations):
- lines 149-153: replace /GSD SDK ready/.test(combined) with
isGsdSdkOnPath(filterNpxFromPath(PATH)) === false
- lines 167-169, 185-189: split filterNpxFromPath result into segments array
and use array.includes() instead of string.includes() on the raw PATH string
- lines 375-377: replace /GSD SDK ready/.test(combined) with
fs.existsSync(shimPath) + isGsdSdkOnPath(filterNpxFromPath(localBin))
All 8 tests pass. lint-no-source-grep: 0 violations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(build-hooks): per-PID staging dir eliminates concurrent-cleanup TOCTOU race
When multiple test before() hooks spawned build-hooks.js concurrently
(--test-concurrency=4), a race existed: Process A would finish all copies,
call rmdirSync('.dist-staging/') in cleanup, then Process B — still in its
copy loop — would call copyFileSync(src, '.dist-staging/hook.pid.ts') and
get ENOENT because the staging directory was gone.
On macOS/Linux, copyFileSync reports the SOURCE path in ENOENT errors when
the destination directory is missing, making the failure appear to be a
missing source file (hooks/gsd-statusline.js) rather than a missing
destination directory. This misled the diagnosis.
Fix: make STAGE_DIR per-PID ('.dist-staging-<pid>/') so each builder owns
its own staging directory. No other process touches it, eliminating all
contention on staging-dir creation and cleanup. Update .gitignore to match
the new 'hooks/.dist-staging-*/' glob.
Reproduces as: CI test matrix (macos-24, ubuntu-22, ubuntu-24) all failing
with ENOENT on hooks/gsd-statusline.js in bug-2136 before() hook. The new
test file added in this PR (bug-3231) shifts the concurrency schedule just
enough to expose the race on every CI run.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: assert on captured console output, not tautological PATH state (CR finding)
The two discarded `captureConsole()` return values in the bug-3231 test
were flagged by CodeRabbit as tautological assertions. Fix:
- Test 1 (transient _npx PATH): capture stdout/stderr and assert the
installer does NOT emit "GSD SDK ready" (the false-positive the PR
fixes), and that it does emit some diagnostic output instead.
- Test 3 (clean install): capture stdout/stderr and assert the installer
DOES emit "GSD SDK ready" after successfully self-linking into a
persistent PATH dir — confirming the positive path works correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>