JuliusBrussee/caveman · error

caveman build: unresolved relative source import

Error message

caveman build: unresolved relative source import ${JSON.stringify(specifier)}

What it means

The source-graph walker resolves relative imports by probing candidate files on disk, tolerating ENOENT while trying extensions, but if no candidate file is found it throws this error naming the unresolved relative specifier. It means a relative import in the traced source graph points at a file that does not exist.

Solutions

  1. Fix the import specifier to match the actual file path/case
  2. Create the missing file at the referenced location
  3. If the module truly doesn't belong in the graph, make the import external/bare or exclude it from the source-graph traversal
  4. Run a quick ls on the importing file's directory to confirm the exact filename and extension

Example fix

// before
import { render } from './Render.tsx';   // file is actually render.tsx
// after
import { render } from './render.tsx';
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync } from 'fs';
import { dirname, resolve } from 'path';
if (!existsSync(resolve(dirname(importerPath), specifier))) {
  throw new Error(`Relative import target missing: ${specifier}`);
}

Try / catch

try {
  await build({ files: sourceFiles(entry) });
} catch (e) {
  if (e.message.includes('unresolved relative source import')) {
    console.error('Fix the import path/case:', e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: expandSourceGraph follows a relative import ('./x', '../y/z') and no matching file (with any supported extension) exists at that location from the importing file's directory.

Common situations: Typo in the relative path; file renamed/moved without updating imports; wrong letter-case on case-sensitive filesystems; import written with an extension variant the resolver does not probe.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/4a48d96736824051. Report an issue: GitHub.

Appendix: source

Thrown at packages/agent/src/source-graph.ts:84

      const candidates = extname(base)
        ? explicitImportCandidates(base, path)
        : [
          base,
          ...RESOLUTION_EXTENSIONS.map((extension) => base + extension),
          ...SOURCE_EXTENSIONS.map((extension) => resolve(base, `index${extension}`)),
        ];
      let found: string | undefined;
      for (const candidate of candidates) {
        try {
          await readFile(candidate);
          found = candidate;
          break;
        } catch (error) {
          if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
        }
      }
      if (!found) {
        throw new Error(`caveman build: unresolved relative source import ${JSON.stringify(specifier)}`);
      }
      if (bare) {
        await collectPackageClosure(packageRoot!, files, packageRoots);
        continue;
      }
      const lexicalFound = found;
      found = await realpath(found);
      const insideRoot = isPathWithin(canonicalRoot, found);
      if (!insideRoot && !bare && !external.has(path)) {
        const lexicalInsideRoot = isPathWithin(canonicalRoot, lexicalFound);
        if (lexicalInsideRoot) {
          throw new Error("caveman build: source graph symlink escapes project root");
        }
        throw new Error("caveman build: imported source escapes project root");
      }
      if (!insideRoot) external.add(found);
      if (!files.has(found)) {
        files.add(found);

View on GitHub (pinned to 3ee70a1026)