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
- Fix the import specifier to match the actual file path/case
- Create the missing file at the referenced location
- If the module truly doesn't belong in the graph, make the import external/bare or exclude it from the source-graph traversal
- 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
- Enable TS 'moduleResolution' + tsc/lint checks to catch missing files pre-build
- Avoid renaming files without IDE-assisted import updates
- Use consistent extension/casing conventions (lowercase filenames)
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
- caveman build: aliased source loader is not lockable in
- caveman build: computed source dependency is not lockable in
- caveman build: imported source escapes project root
- caveman build: unresolved package dependency
- caveman build: unresolved source dependency
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)