JuliusBrussee/caveman · error
caveman build: computed source dependency is not lockable in
Error message
caveman build: computed source dependency is not lockable in ${JSON.stringify(path)} What it means
esmSourceSpecifiers() throws when an import/export declaration has no statically known specifier string (es-module-lexer reports n === undefined for it). The build lock must enumerate every dependency edge as a literal; a computed dependency (e.g. import(someVariable) or export * from template) cannot be locked, so the file path is reported and the build aborts.
Source
Thrown at packages/agent/src/source-graph.ts:285
return child === "" || (!isAbsolute(child) && child !== ".." &&
!child.startsWith("../") && !child.startsWith("..\\"));
}
function esmSourceSpecifiers(source: string, path: string): string[] {
let imports: ReturnType<typeof parse>[0];
try {
[imports] = parse(source, path);
} catch (error) {
throw new Error(
`caveman build: source module syntax is not lexable in ${JSON.stringify(path)}`,
{ cause: error },
);
}
const specifiers: string[] = [];
for (const item of imports) {
if (item.d === -2) continue; // import.meta is metadata, not a dependency.
if (item.n === undefined) {
throw new Error(`caveman build: computed source dependency is not lockable in ${JSON.stringify(path)}`);
}
specifiers.push(item.n);
}
return specifiers;
}
function typescriptSourceSyntax(
source: string,
path: string,
code: Uint8Array,
): { lexableSource: string; specifiers: string[] } {
if (!TYPESCRIPT_SOURCE_EXTENSIONS.has(extname(path))) {
return { lexableSource: source, specifiers: [] };
}
const masks: Array<{ start: number; end: number }> = [];
const specifiers: string[] = [];
let precedingExportEnd: number | undefined;
for (const match of source.matchAll(TYPESCRIPT_IMPORT_EXPORT_PATTERN)) {View on GitHub (pinned to 27d5a3981a)
Solutions
- Replace computed imports with a static map of literal specifiers and select from it at runtime.
- If only some paths are possible, enumerate them all as literal imports (optionally via a generated file).
- Move truly dynamic loading behind an API boundary that stays outside the locked source graph.
Example fix
// before
const mod = await import(`./locales/${locale}.ts`);
// after
const locales = { en: () => import("./locales/en.ts"), de: () => import("./locales/de.ts") };
const mod = await locales[locale](); Defensive patterns
Strategy: validation
Validate before calling
import { parse } from "es-module-lexer";
function hasComputedImports(source: string, path: string): boolean {
const [imports] = parse(source, path);
return imports.some((item) => item.d !== -2 && item.n === undefined);
}
if (hasComputedImports(src, file)) throw new Error(`computed import in ${file}`); Type guard
const isStaticSpecifier = (item: { d: number; n: string | undefined }): boolean =>
item.d === -2 || item.n !== undefined; Try / catch
try {
await buildSourceGraph(root);
} catch (error) {
if (error instanceof Error && error.message.includes("computed source dependency is not lockable")) {
// replace import(variable) with a literal specifier map
} else throw error;
} Prevention
- Never build import specifiers from variables inside locked sources.
- Use a static map of literal dynamic imports for finite path sets.
- Add a lint rule banning `import(` with non-literal arguments (e.g. no-dynamic-require equivalents).
When it happens
Trigger: Dynamic import with a non-literal argument: const mod = await import(locale); re-export from a computed string; import.meta.dynamic-ish patterns that defeat static analysis.
Common situations: Plugin/lazy-loading systems that build module names at runtime; i18n loaders importing locale files by variable; refactors that replace static imports with data-driven ones.
Related errors
- caveman build: aliased source loader is not lockable in ${JS
- caveman build: dataResidency is not enforced yet; refusing t
- caveman build: requiredFixturePassRate must be in (0,1]
- caveman build: qualityRetention must be in (0,1]
- caveman build: config must export defineBuild()
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/ed7a3d1345d9ab59.
Report an issue: GitHub.