santifer/career-ops · error · Error

targetDir is not an existing directory

Error message

targetDir is not an existing directory: ${targetDir}

What it means

seedFixture() requires targetDir to be an existing directory and throws this when the path does not exist or statSync shows it is not a directory (e.g. a file). The function deliberately does not create directories; callers must provision the target first.

Solutions

  1. Create the directory before calling: fs.mkdirSync(targetDir, { recursive: true }).
  2. Confirm the path is a directory, not a file: fs.statSync(targetDir).isDirectory().
  3. Use a per-test fresh temp dir (fs.mkdtempSync(path.join(os.tmpdir(), 'fixture-'))).
  4. Resolve relative paths against a known base (path.resolve(__dirname, ...)) to avoid cwd surprises.

Example fix

// before
await seedFixture(tmpDir); // tmpDir never created
// after
fs.mkdirSync(tmpDir, { recursive: true });
await seedFixture(tmpDir);
Defensive patterns

Strategy: validation

Validate before calling

import { mkdirSync, existsSync, statSync } from 'node:fs';
if (!existsSync(targetDir) || !statSync(targetDir).isDirectory()) {
  mkdirSync(targetDir, { recursive: true });
}
await seedFixture(targetDir, { state });

Type guard

const isExistingDirectory = (p) => existsSync(p) && statSync(p).isDirectory();

Try / catch

try {
  await seedFixture(targetDir);
} catch (err) {
  if (err.message.startsWith('targetDir is not an existing directory')) {
    fs.mkdirSync(targetDir, { recursive: true });
    await seedFixture(targetDir);
  } else throw err;
}

Prevention

When it happens

Trigger: seedFixture('/nonexistent/path') (directory never created); seedFixture('/tmp/a-file.txt') (path is a regular file); a race where another process deleted the directory between checks; a typo'd or relative-to-wrong-cwd path.

Common situations: Test setup forgetting fs.mkdir(tempDir, {recursive:true}) before seeding; pointing at a scratch file instead of a directory; temp-dir cleanup running concurrently with seeding; using a path relative to a different working directory in CI.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/7a413484381b3eea. Report an issue: GitHub.

Appendix: source

Thrown at seed-fixture.mjs:67

  // deliberately exempt). Leaving it in would make a whole second repository an
  // allowlisted fixture state and seed it into the install under test (#3762).
  return readdirSync(FIXTURES).sort()
    .filter((n) => statSync(join(FIXTURES, n)).isDirectory() && !isNestedCheckout(join(FIXTURES, n)));
}

// Strict allowlist: a state must be one of the real fixture subdirectories.
// Membership in listStates() (basenames only) inherently rejects both unknown
// states and traversal strings ('../x', 'state/../..'), so no join() ever
// escapes FIXTURES.
function assertKnownState(state) {
  const states = listStates();
  if (!states.includes(state)) {
    throw new Error(`Unknown fixture state: ${state} (have: ${states.join(', ') || 'none'})`);
  }
}

export function seedFixture(targetDir, { state = DEFAULT_STATE } = {}) {
  if (!existsSync(targetDir) || !statSync(targetDir).isDirectory()) throw new Error(`targetDir is not an existing directory: ${targetDir}`);
  assertKnownState(state);
  const src = join(FIXTURES, state);
  // expected.json is harness metadata, not user data — never seed it.
  const files = walk(src).filter((f) => f !== 'expected.json');
  const manifest = {};
  for (const f of files) {
    const dest = join(targetDir, f);
    mkdirSync(dirname(dest), { recursive: true });
    cpSync(join(src, f), dest);
    manifest[f] = createHash('sha256').update(readFileSync(dest)).digest('hex');
  }
  return { state, files, manifest };
}

export function loadExpectations(state = DEFAULT_STATE) {
  assertKnownState(state);
  return JSON.parse(readFileSync(join(FIXTURES, state, 'expected.json'), 'utf-8'));
}

View on GitHub (pinned to aac998c7ed)