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

  1. Read the `cause` to see the underlying failure, then fix that root issue.
  2. Reinstall dependencies cleanly (rm -rf node_modules package-lock.json && npm install).
  3. Pin/adjust versions so the named dependency is actually published and available at the required range.
  4. 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

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


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)