JuliusBrussee/caveman · error
caveman build: unresolved package dependency
Error message
caveman build: unresolved package dependency ${JSON.stringify(dependency)} What it means
The user-facing wrapper error thrown by collectPackageClosure when a REQUIRED dependency (in dependencies/devDependencies, not optional) cannot be resolved during closure expansion. It names the dependency in the message and attaches the underlying resolution error as `cause`. Optional dependencies that fail are intentionally skipped and never reach this throw.
Solutions
- Read the `cause` to see the underlying failure, then fix that root issue.
- Reinstall dependencies cleanly (rm -rf node_modules package-lock.json && npm install).
- Pin/adjust versions so the named dependency is actually published and available at the required range.
- If the dependency is genuinely optional for your build, move it to optionalDependencies.
Example fix
// before: lockfile references a yanked version "some-dep": "npm:some-dep@2.0.0" // yanked // after "some-dep": "^2.1.0" // then reinstall
Defensive patterns
Strategy: try-catch
Validate before calling
import { createRequire } from "node:module";
function requireDepsResolvable(pkgJsonPath) {
const pkg = require(pkgJsonPath);
const req = createRequire(pkgJsonPath);
return Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }).filter((d) => { try { req.resolve(d); return false; } catch { return true; } });
} Type guard
null
Try / catch
try {
await graph.expand();
} catch (e) {
if (e instanceof Error && e.message.startsWith("caveman build: unresolved package dependency")) {
const dep = JSON.parse(e.message.split("dependency ")[1] ?? '""');
console.error(`Install or fix required dependency: ${dep}`);
}
throw e;
} Prevention
- Keep lockfile and package.json in sync (CI check with npm ls).
- Run npm ls / pnpm ls to detect broken trees before building.
- Prefer versions that exist and aren't yanked; verify ranges against the registry.
When it happens
Trigger: collectPackageClosure (recursively, via expandSourceGraph) encounters a required dependency whose package.json cannot be found, or whose recursive closure threw; the catch block rethrows because `required.has(dependency)` is true.
Common situations: Fresh checkout without install; corrupted node_modules; registry package that removed a transitive dependency; lockfile out of sync with package.json; offline install with missing cache.
Understand the failure class
Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.
Related errors
- caveman build: aliased source loader is not lockable in
- caveman build: computed source dependency is not lockable in
- caveman build: unresolved relative source import
- caveman build: unresolved source dependency
- import export not found
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/791faa9a269af499.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/source-graph.ts:186
const packageJSON = JSON.parse(await readFile(packageJSONPath, "utf8")) as {
dependencies?: Record<string, unknown>;
optionalDependencies?: Record<string, unknown>;
peerDependencies?: Record<string, unknown>;
};
await collectPackageFiles(canonicalRoot, files);
const required = new Set(Object.keys(packageJSON.dependencies ?? {}));
const optional = new Set([
...Object.keys(packageJSON.optionalDependencies ?? {}),
...Object.keys(packageJSON.peerDependencies ?? {}),
]);
for (const dependency of [...new Set([...required, ...optional])].sort()) {
try {
const childPackageJSON = findPackageJSON(dependency, pathToFileURL(packageJSONPath));
if (childPackageJSON === undefined) throw new Error("package not found");
await collectPackageClosure(dirname(childPackageJSON), files, visitedRoots);
} catch (error) {
if (!required.has(dependency)) continue;
throw new Error(`caveman build: unresolved package dependency ${JSON.stringify(dependency)}`, {
cause: error,
});
}
}
}
async function collectPackageFiles(directory: string, files: Set<string>): Promise<void> {
const entries = await opendir(directory);
for await (const entry of entries) {
if (entry.name === "node_modules" || entry.name === ".git") continue;
const path = resolve(directory, entry.name);
if (entry.isDirectory()) {
await collectPackageFiles(path, files);
} else if (entry.isFile()) {
files.add(path);
} else if (entry.isSymbolicLink()) {
throw new Error(`caveman build: package artifact symlink is not lockable: ${JSON.stringify(path)}`);
}View on GitHub (pinned to 3ee70a1026)