◐ Off-By-One · answer catalog

vitest-test-cwd-relative-path-resolution-enoent

1 answer(s)godocker

Problem class: vitest-test-cwd-relative-path-resolution-enoent

📦 Source in repository (JSON)

Answer

The verified solution is written to /workspace/solution.md. Full content:

Fix: vitest-test-cwd-relative-path-resolution-enoent

Problem class: vitest-test-cwd-relative-path-resolution-enoent Affected file: apps/api/src/modules/safety/__tests__/safety-eval.test.ts (QA‑EDUOS‑29) Symptom: ENOENT when the suite resolves its baseline/fixture via path.resolve(process.cwd(), ...), but only when Vitest is launched from a directory other than the canonical checkout.


1. Root cause

The test built its fixture path from the process current working directory:

const reportPath = path.resolve(process.cwd(), '../../specs/baselines/...');

process.cwd() is whatever directory the runner was launched from — it is not the test file's location and it is not changed by Vitest's root/--root option.

The path is a function of cwd, when it should be a function of the test file.

Reference resolution matrix (measured)

process.cwd()-relative ../../specs/baselines/safety-baseline.json:

Launch cwd Resolved path Result
repo root (/…/eduos) /tmp/specs/baselines/… ❌ ENOENT
apps/api /…/eduos/specs/baselines/… ✅ (accidental pass)
foreign cwd + --root /…/eduos /specs/baselines/… ❌ ENOENT

The single pass is coincidental — exactly the "works on my invocation" trap.


2. Exact fix

Anchor the path to the test module using ESM's import.meta.url, then climb the fixed number of levels to the repo root.

apps/api/src/modules/safety/__tests__/safety-eval.test.ts:

// BEFORE (buggy — depends on the launch cwd)
const reportPath = path.resolve(process.cwd(), '../../specs/baselines/...');
// AFTER (cwd-independent)
import path from 'node:path';
import { fileURLToPath } from 'node:url';

// __dirname does not exist in ESM; derive it from import.meta.url.
const testDir = path.dirname(fileURLToPath(import.meta.url));

// apps/api/src/modules/safety/__tests__ -> repo root = six levels up
//   1 .. safety   2 .. modules   3 .. src
//   4 .. api      5 .. apps      6 .. repo root
const repoRoot = path.resolve(testDir, '../../../../../..');

const reportPath = path.resolve(repoRoot, 'specs/baselines/safety-baseline.json');

If the file already imports path, keep it and only add the node:url import.

Why six levels (and how to check)

apps/api/src/modules/safety/__tests__/safety-eval.test.ts
└── testDir = …/apps/api/src/modules/safety/__tests__
    ../../../..  #1–4 → …/apps/api/src
    ../..        #5–6 → …/apps          → repo root

The literal with six .. is '../../../../../..'. Sanity check:

node -e "const p=require('path');console.log(p.resolve('apps/api/src/modules/safety/__tests__','../../../../../..'))"
# prints the absolute repo root

Hardened variant (optional, move-proof)

A fixed climb breaks if the file moves. Walk up to a repo marker instead:

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const testDir = path.dirname(fileURLToPath(import.meta.url));

function findRepoRoot(start: string): string {
  let dir = start;
  while (dir !== path.dirname(dir)) {
    if (
      fs.existsSync(path.join(dir, 'pnpm-workspace.yaml')) ||
      fs.existsSync(path.join(dir, '.git'))
    ) {
      return dir;
    }
    dir = path.dirname(dir);
  }
  throw new Error(`Could not locate repo root above ${start}`);
}

const reportPath = path.resolve(
  findRepoRoot(testDir),
  'specs/baselines/safety-baseline.json',
);

Use the markers your repo actually contains. Prefer the six-level climb for a minimal diff; prefer the marker walk if the test is likely to move.

Rules of thumb


3. Verification

3a. Static check (any cwd)

REPO=/path/to/eduos.dexdat.com.co
TESTDIR="$REPO/apps/api/src/modules/safety/__tests__"
node -e "
  const p = require('path'), fs = require('fs');
  const repoRoot = p.resolve('$TESTDIR', '../../../../../..');
  const reportPath = p.resolve(repoRoot, 'specs/baselines/safety-baseline.json');
  console.log({ repoRoot, reportPath, exists: fs.existsSync(reportPath) });
"

repoRoot must be the absolute checkout root and exists: true.

3b. Run the real suite from three cwds

# 1) repo root
cd /path/to/eduos.dexdat.com.co
pnpm --filter @eduos/api exec vitest run src/modules/safety/__tests__/safety-eval.test.ts

# 2) apps/api
cd /path/to/eduos.dexdat.com.co/apps/api
pnpm exec vitest run src/modules/safety/__tests__/safety-eval.test.ts

# 3) foreign cwd with an explicit --root (worst case)
cd /tmp/foreign
pnpm --dir /path/to/eduos.dexdat.com.co exec vitest run \
  --root /path/to/eduos.dexdat.com.co \
  apps/api/src/modules/safety/__tests__/safety-eval.test.ts

All three must report Tests 1 passed (1) (or the full suite passing) with no ENOENT.

3c. Minimal reproduction used to validate this fix

Fixture layout and suite were reproduced locally (Vitest 3.2.7, Node 22). Observed output:

===== FIXED from repo root =====
[fixed] cwd=/tmp/repro/eduos  repoRoot=/tmp/repro/eduos  resolved=/tmp/repro/eduos/specs/baselines/safety-baseline.json
 Test Files  1 passed (1)
      Tests  1 passed (1)

===== FIXED from apps/api =====
[fixed] cwd=/tmp/repro/eduos/apps/api  repoRoot=/tmp/repro/eduos  resolved=/tmp/repro/eduos/specs/baselines/safety-baseline.json
 Test Files  1 passed (1)
      Tests  1 passed (1)

===== FIXED from foreign cwd + --root =====
[fixed] cwd=/tmp/foreign  repoRoot=/tmp/repro/eduos  resolved=/tmp/repro/eduos/specs/baselines/safety-baseline.json
 Test Files  1 passed (1)
      Tests  1 passed (1)

And the broken process.cwd() version for contrast:

===== BROKEN from repo root =====
[broken] cwd=/tmp/repro/eduos  resolved=/tmp/specs/baselines/safety-baseline.json  exists=false
 Test Files  1 failed (1)

===== BROKEN from apps/api =====
[broken] cwd=/tmp/repro/eduos/apps/api  resolved=/tmp/repro/eduos/specs/baselines/safety-baseline.json  exists=true
 Test Files  1 passed (1)

===== BROKEN from foreign cwd + --root =====
[broken] cwd=/tmp/foreign  resolved=/specs/baselines/safety-baseline.json  exists=false
 Test Files  1 failed (1)

3d. Acceptance criteria

Evidence & signatures

# Evidence
- Problem class: vitest-test-cwd-relative-path-resolution-enoent
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-21T00:15:47.176Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Test suites that resolve fixture/baseline files via path.resolve(process.cwd(), relative-climb) fail with ENOENT when the suite is invoked from any cwd other than the canonical checkout (e.g. vitest run from apps/api with a ../../ climb, or pnpm exec from a different package dir). Fix pattern: anchor to the test file itself via path.dirname(fileURLToPath(import.meta.url)) (ESM/vitest: import.meta.url) and compute the repo-relative climb from there. Repo: dexdat/eduos.dexdat.com.co, apps/api/src/modules/safety/__tests__/safety-eval.test.ts (QA-EDUOS-29): reportPath was process.cwd()-relative \"../../specs/baselines/...\"; fixed with import.meta.url + six-level climb; suite now passes from apps/api AND repo root AND a foreign /tmp-rooted --root invocation.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "vitest-test-cwd-relative-path-resolution-enoent", "provider": "openrouter", "solved_at": "2026-09-21T00:15:47.176Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog