JuliusBrussee/caveman · error

caveman build: unresolved package dependency ${JSON.stringif

Error message

caveman build: unresolved package dependency ${JSON.stringify(dependency)}

What it means

While collecting a package's dependency closure, a dependency listed in package.json's dependencies could not be resolved (its package.json was not found). Required dependencies are hard failures; optional and peer dependencies are skipped silently when unresolvable. The failing dependency name is embedded in the error and the underlying cause is attached.

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 27d5a3981a)

Solutions

  1. Delete node_modules and lockfile-unfriendly state, then reinstall cleanly (rm -rf node_modules && pnpm install).
  2. Check the named dependency in the error against the lockfile — a lockfile/install mismatch usually explains it.
  3. If the package genuinely needs the dependency only optionally, its maintainers should move it to optionalDependencies; file an issue rather than patching locally.

Example fix

# before
pnpm install --no-optional # or a partial install

# after
rm -rf node_modules && pnpm install
Defensive patterns

Strategy: validation

Validate before calling

import { createRequire } from "node:module";
import { readFileSync } from "node:fs";
function requiredDepsResolvable(packageDir: string): void {
  const pkg = JSON.parse(readFileSync(resolve(packageDir, "package.json"), "utf8"));
  const req = createRequire(resolve(packageDir, "index.js"));
  for (const dep of Object.keys(pkg.dependencies ?? {})) {
    try { req.resolve(`${dep}/package.json`); }
    catch { throw new Error(`required dependency missing: ${dep}`); }
  }
}

Try / catch

try {
  await buildSourceGraph(root);
} catch (error) {
  if (error instanceof Error && error.message.startsWith("caveman build: unresolved package dependency")) {
    // dependency name is quoted; clean reinstall node_modules
  } else throw error;
}

Prevention

When it happens

Trigger: An installed package's package.json declares a runtime dependency that is missing from node_modules — broken install, pruned store, or a manually-edited package tree.

Common situations: Interrupted or partial pnpm/npm install; node_modules copied between machines; a dependency whose transitive install failed; using --no-optional or aggressive pruning that removed a required transitive package.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/791faa9a269af499. Report an issue: GitHub.