JuliusBrussee/caveman · error
caveman build: aliased source loader is not lockable in
Error message
caveman build: aliased source loader is not lockable in ${JSON.stringify(path)} What it means
During build, the caveman source-graph scanner (legacySourceSpecifiers in packages/agent/src/source-graph.ts) refuses to analyze a project file that creates an aliased dynamic loader — e.g. `const req = require(...)`, `const req = createRequire(...)`, or `const u = URL` — because such an alias makes the file's import surface statically unanalyzable. The build/lock system requires every reachable dependency edge to be provable from source text; an aliased loader could resolve arbitrary modules at runtime, so the file cannot be locked.
Solutions
- Remove the aliased loader and import the module statically with an ESM `import` statement so the graph can resolve the edge.
- Move the file out of the scanned project graph (make it an installed dependency artifact or exclude it) if it legitimately needs dynamic loading.
- For JSON config, use `import config from './x.json' with { type: 'json' }` instead of require-ing it.
- Keep the dynamic load behind a worker or subprocess boundary that is not part of the locked source graph.
Example fix
// before
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const data = require("./config.json");
// after
import data from "./config.json" with { type: "json" }; Defensive patterns
Strategy: validation
Validate before calling
// Run before caveman build; fails fast on aliased loaders
const loaderAlias = /(const|let|var)\s+\w+\s*=\s*(require|URL)\b|createRequire\s*\(/;
for (const file of projectSourceFiles) {
if (loaderAlias.test(stripComments(readFileSync(file, "utf8")))) {
throw new Error(`aliased loader in ${file} blocks locked build`);
}
} Prevention
- Use static ESM `import` statements exclusively in project source reachable from agent definitions.
- Keep CommonJS interop (createRequire) out of scanned project files; isolate it in external packages or workers.
- Load JSON/config with import attributes instead of require.
- Run `caveman check` in CI to catch unanalyzable source before attempting a locked build.
When it happens
Trigger: Running `caveman build`/`dev`/`check` over a source file reachable from the agent definition that contains `createRequire(import.meta.url)`, or assigns `require` or `URL` to a local variable via const/let/var (skipping comments), inside a file the graph scans.
Common situations: Utility modules that use CommonJS interop (`const require = createRequire(import.meta.url)`) in an otherwise-ESM project; code loading JSON or native addons via an aliased require; vendored helper files pulled into the graph; copying CJS idioms into an ESM project.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- caveman build: computed source dependency is not lockable in
- caveman build: unresolved package dependency
- 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/5403cf19b5bc1bf5.
Report an issue: GitHub.
Appendix: 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 3ee70a1026)