From aacdfcac4045633021d9b78b640051f25380b217 Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Fri, 17 Jul 2026 18:03:25 -0700 Subject: [PATCH 1/4] feat(startup): log the resolved Harper binary and warn on node_modules/dist ambiguity Which binary runs is the most consequential decision the harness makes and it was invisible: resolving the auto-installed 'harper' peer from node_modules while the caller expected their local build produces wholesale test failures with no hint of the cause (#23). Log the resolved path and its source once per distinct path, and warn with the override when the node_modules copy wins while ./dist/bin/harper.js also exists. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01RXx78W5LxbvxEdSyrB1vvn --- src/harperLifecycle.ts | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/harperLifecycle.ts b/src/harperLifecycle.ts index 0fe4ed2..fbdb389 100644 --- a/src/harperLifecycle.ts +++ b/src/harperLifecycle.ts @@ -222,7 +222,7 @@ function getHarperScript(harperBinPath?: string): string { // 1. Explicit option if (harperBinPath) { ok(existsSync(harperBinPath), `Harper script not found at provided harperBinPath: ${harperBinPath}`); - return harperBinPath; + return logResolvedHarperScript(harperBinPath, 'harperBinPath option'); } // 2. Environment variable @@ -232,7 +232,7 @@ function getHarperScript(harperBinPath?: string): string { existsSync(envPath), `Harper script not found at HARPER_INTEGRATION_TEST_INSTALL_SCRIPT path: ${envPath}` ); - return envPath; + return logResolvedHarperScript(envPath, 'HARPER_INTEGRATION_TEST_INSTALL_SCRIPT'); } // 3. Auto-resolve from node_modules @@ -240,7 +240,7 @@ function getHarperScript(harperBinPath?: string): string { const require = createRequire(import.meta.url); const resolved = require.resolve('harper/dist/bin/harper.js'); if (existsSync(resolved)) { - return resolved; + return logResolvedHarperScript(resolved, 'node_modules'); } } catch { // harper package not found in node_modules @@ -251,7 +251,7 @@ function getHarperScript(harperBinPath?: string): string { while (true) { const potentialPath = join(currentDir, 'dist/bin/harper.js'); if (existsSync(potentialPath)) { - return potentialPath; + return logResolvedHarperScript(potentialPath, 'ancestor dist'); } const parentDir = dirname(currentDir); if (parentDir === currentDir) { @@ -269,6 +269,33 @@ function getHarperScript(harperBinPath?: string): string { ); } +const loggedHarperScripts = new Set(); + +/** + * Logs which Harper binary was resolved and how, once per distinct path. + * + * Which binary runs is the single most consequential (and previously invisible) + * decision the harness makes: resolving a stale `harper` package from + * node_modules while the caller expected their local build produces confusing + * wholesale test failures with no hint of the cause. When the `node_modules` + * path wins while `./dist/bin/harper.js` also exists, warn with the override. + */ +function logResolvedHarperScript(scriptPath: string, source: string): string { + if (loggedHarperScripts.has(scriptPath)) return scriptPath; + loggedHarperScripts.add(scriptPath); + console.log(`[integration-testing] Using Harper at ${scriptPath} (via ${source})`); + if (source === 'node_modules') { + const cwdDist = join(process.cwd(), 'dist/bin/harper.js'); + if (existsSync(cwdDist) && cwdDist !== scriptPath) { + console.warn( + `[integration-testing] Warning: resolved the 'harper' package from node_modules, but ${cwdDist} also exists. ` + + `If you meant to test the local build, set HARPER_INTEGRATION_TEST_INSTALL_SCRIPT=dist/bin/harper.js.` + ); + } + } + return scriptPath; +} + /** * Strips ANSI escape sequences (colors, bold, underline, cursor movement, etc.) from a string. */ From cb4fc98204e291516fee17de840b8bebb6da0336 Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Fri, 17 Jul 2026 18:03:25 -0700 Subject: [PATCH 2/4] fix(setup-loopback): verify aliases landed before reporting success MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A partial run (expired sudo credential mid-loop, interruption) still printed the ✓ success line, leaving the test runner failing later on the missing addresses with no connection back to the setup step. Re-check every address after the loop; on any miss, print the exact per-address fix commands and exit non-zero. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01RXx78W5LxbvxEdSyrB1vvn --- scripts/setup-loopback.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/scripts/setup-loopback.sh b/scripts/setup-loopback.sh index 7870e89..a88b958 100755 --- a/scripts/setup-loopback.sh +++ b/scripts/setup-loopback.sh @@ -58,4 +58,22 @@ for i in $(seq $START $END); do $SUDO ifconfig lo0 alias 127.0.0.$i netmask 255.255.255.255 up done -echo "✓ Configured $COUNT loopback addresses (127.0.0.$START-127.0.0.$END)" \ No newline at end of file +# Verify every alias actually landed before claiming success. A partial run +# (expired sudo credential mid-loop, interruption) previously still printed ✓, +# leaving the test runner failing later on the missing addresses. +MISSING=() +for i in $(seq $START $END); do + if ! ifconfig lo0 | grep -q "inet 127\.0\.0\.$i "; then + MISSING+=("127.0.0.$i") + fi +done + +if [ ${#MISSING[@]} -gt 0 ]; then + echo "✗ ${#MISSING[@]} loopback address(es) failed to configure. Fix with:" + for addr in "${MISSING[@]}"; do + echo " sudo ifconfig lo0 alias $addr netmask 255.255.255.255 up" + done + exit 1 +fi + +echo "✓ Configured and verified $COUNT loopback addresses (127.0.0.$START-127.0.0.$END)" \ No newline at end of file From c17d928eead94fc07fadcb2e800fce364ac905ba Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Fri, 17 Jul 2026 18:17:48 -0700 Subject: [PATCH 3/4] fix(setup-loopback): announce the range up front; capture ifconfig output once The pre-loop range announcement doubles as a tell when two checkouts run package versions with different pool conventions. Single ifconfig capture for the verify loop per review. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01RXx78W5LxbvxEdSyrB1vvn --- scripts/setup-loopback.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/setup-loopback.sh b/scripts/setup-loopback.sh index a88b958..60fdd80 100755 --- a/scripts/setup-loopback.sh +++ b/scripts/setup-loopback.sh @@ -41,6 +41,9 @@ if [ "$COUNT" -lt 1 ] || [ "$COUNT" -gt "$MAX" ]; then fi END=$((START + COUNT - 1)) +# Announce the range up front — also a tell when two checkouts run different +# package versions with different pool conventions (0.3.x started at 127.0.0.1). +echo "Configuring loopback addresses 127.0.0.$START-127.0.0.$END on lo0..." for i in $(seq $START $END); do # Use a host (/32) netmask, not the implicit class-A /8. Without an explicit netmask, # macOS gives each 127.0.0.x alias a 255.0.0.0 mask, so every alias claims to own the @@ -62,8 +65,9 @@ done # (expired sudo credential mid-loop, interruption) previously still printed ✓, # leaving the test runner failing later on the missing addresses. MISSING=() +LO0_STATE=$(ifconfig lo0) for i in $(seq $START $END); do - if ! ifconfig lo0 | grep -q "inet 127\.0\.0\.$i "; then + if ! echo "$LO0_STATE" | grep -q "inet 127\.0\.0\.$i "; then MISSING+=("127.0.0.$i") fi done From 154cba3e97060447e03ec9e58e65d616531f10b9 Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Fri, 17 Jul 2026 22:08:46 -0700 Subject: [PATCH 4/4] feat(startup): walk ancestors for the local-build warning (review) process.cwd() alone misses the local build when tests run from a repo subdirectory; mirror resolution step 4's ancestor walk. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01RXx78W5LxbvxEdSyrB1vvn --- src/harperLifecycle.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/harperLifecycle.ts b/src/harperLifecycle.ts index fbdb389..026ee03 100644 --- a/src/harperLifecycle.ts +++ b/src/harperLifecycle.ts @@ -285,12 +285,21 @@ function logResolvedHarperScript(scriptPath: string, source: string): string { loggedHarperScripts.add(scriptPath); console.log(`[integration-testing] Using Harper at ${scriptPath} (via ${source})`); if (source === 'node_modules') { - const cwdDist = join(process.cwd(), 'dist/bin/harper.js'); - if (existsSync(cwdDist) && cwdDist !== scriptPath) { - console.warn( - `[integration-testing] Warning: resolved the 'harper' package from node_modules, but ${cwdDist} also exists. ` + - `If you meant to test the local build, set HARPER_INTEGRATION_TEST_INSTALL_SCRIPT=dist/bin/harper.js.` - ); + // Mirror resolution step 4's ancestor walk so the warning fires even when + // tests run from a subdirectory of the repo that holds the local build. + let currentDir = process.cwd(); + while (true) { + const localDist = join(currentDir, 'dist/bin/harper.js'); + if (existsSync(localDist) && localDist !== scriptPath) { + console.warn( + `[integration-testing] Warning: resolved the 'harper' package from node_modules, but ${localDist} also exists. ` + + `If you meant to test the local build, set HARPER_INTEGRATION_TEST_INSTALL_SCRIPT=${localDist}.` + ); + break; + } + const parentDir = dirname(currentDir); + if (parentDir === currentDir) break; + currentDir = parentDir; } } return scriptPath;