Add integration-tests CI and fix harness CLI resolution#5
Conversation
The reference repo was already on harper@^5 with @harperfast/integration-testing
and an integrationTests/ suite, but two gaps would prevent its tests from ever
running:
- No .github/workflows/integration-tests.yml existed, so integration tests had
no CI gate. Added the standard workflow (Node 22/24/26, actions pinned to
commit hashes).
- The test did not pass `harperBinPath`. harper's `exports` map only exposes
".", so the harness's default `require.resolve('harper/dist/bin/harper.js')`
throws ERR_PACKAGE_PATH_NOT_EXPORTED and the cwd-ancestor fallback finds no
dist/bin/harper.js, so getHarperScript() throws "Harper CLI script not found"
before Harper ever starts. Resolve the CLI from harper's exported main entry
and pass it via the documented `harperBinPath` escape hatch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the integration tests to explicitly resolve the harper binary path and pass it to setupHarperWithFixture, which prevents an ERR_PACKAGE_PATH_NOT_EXPORTED error. Feedback suggests using the native import.meta.resolve instead of creating a CommonJS require helper via createRequire to simplify the ESM resolution.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
| integration-tests: | ||
| name: Integration Tests (Node ${{ matrix.node-version }}) | ||
| needs: [generate-node-version-matrix] | ||
| runs-on: ubuntu-latest |
There was a problem hiding this comment.
Missing job-level timeout — the integration-tests job has no timeout-minutes. GitHub's default is 6 hours. The startupTimeoutMs: 60000 inside the test guards Harper's startup, but if a test or Harper teardown hangs afterwards the job will burn the full 6-hour budget before failing.
Suggest adding timeout-minutes: 15 (or whatever fits your expected run time) at the job level:
integration-tests:
name: Integration Tests (Node ${{ matrix.node-version }})
needs: [generate-node-version-matrix]
runs-on: ubuntu-latest
timeout-minutes: 15| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | ||
|
|
There was a problem hiding this comment.
No npm cache configured — the setup-node step is missing cache: 'npm'. With three matrix legs (Node 22, 24, 26) each doing a full npm ci, every run re-downloads the full dependency tree. Adding caching typically cuts install time from ~60s to ~5s per leg.
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'| // harper's exports map only exposes ".", so resolving 'harper/dist/bin/harper.js' | ||
| // (the harness's default auto-resolution) throws ERR_PACKAGE_PATH_NOT_EXPORTED. Resolve the CLI | ||
| // from harper's exported main entry and pass it explicitly via the harness escape hatch. | ||
| const harperBinPath = resolve(dirname(fileURLToPath(import.meta.resolve('harper'))), 'bin/harper.js'); |
There was a problem hiding this comment.
Top-level module evaluation: unhandled throw produces no TAP output — import.meta.resolve('harper') executes at module-load time, before any test runs. If it throws (e.g., harper not installed, or a future harper version that changes its exports map), the entire test file fails to load. The node:test runner reports this as an uncaught exception—not a test failure—so the harper-integration-test-run output is empty/silent, the log artifact has nothing to upload, and the error message is buried in runner stderr rather than the TAP stream.
Moving this into before() (or wrapping in try/catch with an explicit assert.fail()) would make resolution failures surface as a named test failure with a helpful message:
void suite('agent-example-harper loads', (ctx: ContextWithHarper) => {
let harperBinPath: string;
before(async () => {
// Resolve inside before() so a failure surfaces as a named test error, not a silent module crash
const harperMain = import.meta.resolve('harper');
harperBinPath = resolve(dirname(fileURLToPath(harperMain)), 'bin/harper.js');
await setupHarperWithFixture(ctx, FIXTURE_PATH, { startupTimeoutMs: 60000, harperBinPath });
});| // harper's exports map only exposes ".", so resolving 'harper/dist/bin/harper.js' | ||
| // (the harness's default auto-resolution) throws ERR_PACKAGE_PATH_NOT_EXPORTED. Resolve the CLI | ||
| // from harper's exported main entry and pass it explicitly via the harness escape hatch. | ||
| const harperBinPath = resolve(dirname(fileURLToPath(import.meta.resolve('harper'))), 'bin/harper.js'); |
There was a problem hiding this comment.
Path resolution encodes harper's internal layout — the fix derives the CLI path as dirname(fileURLToPath(main)) + 'bin/harper.js'. This works today because harper's main entry (dist/index.js) and its bin (dist/bin/harper.js) both live under dist/. If harper ever separates them (e.g., moves main to dist/lib/index.js), this silently resolves to the wrong path and the harness either crashes or spawns the wrong binary.
A more stable alternative is the HARPER_INTEGRATION_TEST_INSTALL_SCRIPT escape hatch the harness already provides (step 2 in getHarperScript). Setting it in the CI workflow avoids touching the test file at all:
- name: Run integration tests
run: npm run test:integration
env:
HARPER_INTEGRATION_TEST_INSTALL_SCRIPT: ./node_modules/harper/dist/bin/harper.js
HARPER_INTEGRATION_TEST_LOG_DIR: /tmp/harper-test-logs
FORCE_COLOR: '1'That keeps the escape hatch in CI config rather than test code, and the explicit path makes the dependency on harper's layout visible and easy to update.
- Add timeout-minutes: 15 to integration-tests job (prevents 6-hour default CI burn if a test or teardown hangs) - Move harperBinPath computation inside before() hook (a top-level throw causes a silent module crash with no TAP output; inside the hook it surfaces as a proper test failure) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
Review follow-up (autonomous agent): Fixed both blocking findings: added |
| @@ -0,0 +1,75 @@ | |||
| name: Integration Tests | |||
There was a problem hiding this comment.
Low: no explicit permissions block
The workflow doesn't declare permissions:, so the GITHUB_TOKEN inherits whatever the repo/org default grants (which can be read-write). This workflow only checks out code and runs tests — it never comments on PRs or pushes — so it needs nothing beyond read access.
Suggested fix: add near the top of the file:
permissions:
contents: read—
Generated by Barber AI
| node-versions: ${{ steps.set-node-versions.outputs.node-versions }} | ||
|
|
||
| steps: | ||
| - name: Checkout code |
There was a problem hiding this comment.
Nit: unneeded checkout in generate-node-version-matrix
This job only derives the node-versions output from the workflow_dispatch input via a shell one-liner — it never reads repository content, so the checkout step does nothing useful.
Suggested fix: drop this step (and the actions/checkout dependency) from the job.
—
Generated by Barber AI
| - name: Set up Node.js | ||
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | ||
| with: | ||
| node-version: ${{ matrix.node-version }} |
There was a problem hiding this comment.
Low: no npm cache configured
All three matrix legs (Node 22/24/26) run a full npm ci with no dependency cache, so each leg re-downloads the entire tree from scratch. actions/setup-node supports built-in caching, and the repo already has a package-lock.json for it to key off.
Suggested fix:
| node-version: ${{ matrix.node-version }} | |
| node-version: ${{ matrix.node-version }} | |
| cache: 'npm' |
—
Generated by Barber AI
| // harper's exports map only exposes ".", so resolving 'harper/dist/bin/harper.js' | ||
| // (the harness's default auto-resolution) throws ERR_PACKAGE_PATH_NOT_EXPORTED. Resolve the CLI | ||
| // from harper's exported main entry and pass it explicitly via the harness escape hatch. | ||
| const harperBinPath = resolve(dirname(fileURLToPath(import.meta.resolve('harper'))), 'bin/harper.js'); |
There was a problem hiding this comment.
Low: harperBinPath derivation assumes harper's internal dist/ layout
resolve(dirname(fileURLToPath(import.meta.resolve('harper'))), 'bin/harper.js') assumes harper's exported main entry (dist/index.js) and its CLI (dist/bin/harper.js) stay siblings under the same dist/ directory — verified true for harper@5.0.11, but nothing pins that relationship. If a future harper release restructures dist/, this resolves to a path that doesn't exist; the harness's existsSync check in getHarperScript() will catch it and throw a clear error rather than silently running the wrong binary, so the failure mode is safe, but it's still a coupling to an unversioned internal detail.
Suggested fix: no code change needed now, but consider a one-line comment linking to the upstream tracking issue (mentioned in the PR description — harper should export its bin path) so future readers know this is a deliberate, tracked workaround rather than an oversight.
—
Generated by Barber AI
Summary
Verification of the v5 reference repo (Path B). The repo was already correctly on
harper@^5.0.11with@harperfast/integration-testing, anintegrationTests/suite, and atest:integrationscript. No v5 migration patterns remain (wasLoadedFromSource,blob.save,harperdbimports/globals all absent). However two gaps would prevent the integration tests from ever running, so this PR fills them:Missing CI workflow — there was no
.github/workflows/integration-tests.yml(and no CI runs in the repo at all). Added the standard workflow: Node matrix22, 24, 26, actions pinned to commit hashes (checkoutv6.0.3,setup-nodev6.4.0,upload-artifactv7.0.1).Harness CLI resolution would break —
integrationTests/app.test.tsdid not passharperBinPath. harper'sexportsmap only exposes".", so the harness's default auto-resolutionrequire.resolve('harper/dist/bin/harper.js')throwsERR_PACKAGE_PATH_NOT_EXPORTED; the cwd-ancestor fallback also finds nodist/bin/harper.js, sogetHarperScript()throws "Harper CLI script not found" before Harper ever starts. Verified empirically against harper@5.0.11 + integration-testing@0.3.1. Fixed by resolving the CLI from harper's exported main entry and passing it via the documentedharperBinPathescape hatch.Verification
npm run test:integrationlocally. With the fix, the harness now gets past CLI resolution (noERR_PACKAGE_PATH_NOT_EXPORTED, no "CLI script not found") and proceeds to Harper startup, where it fails only at the macOS loopback bind (EADDRNOTAVAILon 127.0.0.2+). That is a known environmental limitation of this dev machine (loopback aliases not configured, requires interactive sudo) — not a code bug. Onubuntu-latestCI the full 127.0.0.0/8 range is available, so the gate runs without aliasing.Notes
env-fix, Claude-model tweak) is unrelated and untouched.harperBinPathworkaround isn't needed in every consumer repo.🤖 Generated with Claude Code