JuliusBrussee/caveman · error

caveman build: aliased source loader is not lockable in ${JS

Error message

caveman build: aliased source loader is not lockable in ${JSON.stringify(path)}

What it means

legacySourceSpecifiers() scans CommonJS-style sources with comment-aware regexes and throws when it detects a loader alias: a call to createRequire(...), or a const/let/var initialized to `require` or `URL`. Once require or import.meta.url is captured into a variable, the scanner can no longer track which files are loaded through it, so the closure is not lockable and the file path is reported.

Source

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

  }
  cursor = skipSourceTrivia(source, cursor);
  const fromEnd = consumeIdentifierToken(source, cursor, "from");
  if (fromEnd === undefined) return undefined; // Local named type export.
  cursor = skipSourceTrivia(source, fromEnd);
  const literal = readQuotedSpecifier(source, cursor, path);
  if (literal === undefined) throw sourceSyntaxError(path);
  return literal.specifier;
}

function legacySourceSpecifiers(source: string, path: string, code: Uint8Array): string[] {
  const trivia = String.raw`(?:\s|\/\*[\s\S]*?\*\/|\/\/[^\r\n]*(?:\r?\n|$))*`;
  const loaderAliases = [
    new RegExp(String.raw`\bcreateRequire${trivia}\(`),
    new RegExp(String.raw`\b(?:const|let|var)${trivia}[A-Za-z_$][\w$]*${trivia}=${trivia}require\b`),
    new RegExp(String.raw`\b(?:const|let|var)${trivia}[A-Za-z_$][\w$]*${trivia}=${trivia}URL\b`),
  ];
  if (loaderAliases.some((pattern) => hasCodeMatch(source, pattern, code))) {
    throw new Error(`caveman build: aliased source loader is not lockable in ${JSON.stringify(path)}`);
  }
  const specifiers: string[] = [];
  for (const match of source.matchAll(LEGACY_LOAD_START_PATTERN)) {
    const start = match.index;
    const keyword = match[0]!;
    if (!code[start] || !isIdentifierTokenAt(source, start, keyword)) continue;
    if (keyword === "module") {
      assertNoModuleRequireAlias(source, start, path);
      continue;
    }
    if (keyword === "Reflect") {
      assertNoReflectRequireAlias(source, start, path, code);
      continue;
    }
    if (keyword === "globalThis") {
      assertNoGlobalLoaderAlias(source, start, path);
      continue;
    }

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Use require(...) / createRequire(...) directly at each load site instead of storing the loader in a variable.
  2. Prefer static ESM import declarations for project-internal files.
  3. Keep loader-wrapping utilities outside the locked project graph (as an installed dependency with a proper closure).

Example fix

// before
const req = createRequire(import.meta.url);
const data = req("./data.json");

// after
import data from "./data.json" with { type: "json" };
Defensive patterns

Strategy: validation

Validate before calling

const ALIAS_PATTERNS = [
  /\bcreateRequire\s*\(/,
  /\b(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*require\b/,
  /\b(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*URL\b/,
];
function usesAliasedLoader(source: string): boolean {
  return ALIAS_PATTERNS.some((p) => p.test(source));
}

Try / catch

try {
  await buildSourceGraph(root);
} catch (error) {
  if (error instanceof Error && error.message.includes("aliased source loader is not lockable")) {
    // file path is in the message; inline require/createRequire at each load site
  } else throw error;
}

Prevention

When it happens

Trigger: Source contains `const req = createRequire(import.meta.url);`, `const r = require;`, or `const myUrl = URL;`-style aliasing followed by indirect loads.

Common situations: Standard-looking ESM/CJS interop shims in utility files; helper modules that wrap require to add caching or logging; code ported from bundler environments where aliasing require is idiomatic.

Related errors


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