denoland/deno · error · Error
v8.startupSnapshot.setDeserializeMainFunction() can only be
Error message
v8.startupSnapshot.setDeserializeMainFunction() can only be called once.
What it means
v8.startupSnapshot.setDeserializeMainFunction(fn, data) registers the single main callback that runs when a startup snapshot is deserialized; the API contract allows exactly one. The polyfill tracks a module-level deserializeMainCalled flag and throws a plain Error on any second call, matching Node.
Source
Thrown at ext/node/polyfills/v8.ts:588
// is an API-surface polyfill that lets modules calling `v8.startupSnapshot`
// load without errors. `isBuildingSnapshot()` always returns false. The
// serialize/deserialize callbacks are stored but never invoked because there
// is no snapshot lifecycle. `setDeserializeMainFunction` invokes the callback
// synchronously so scripts that register a deserialize main still run their
// entry point in plain Deno runs.
// deno-lint-ignore no-explicit-any
type SnapshotCallback = (data: any) => unknown;
const serializeCallbacks: { fn: SnapshotCallback; data: unknown }[] = [];
const deserializeCallbacks: { fn: SnapshotCallback; data: unknown }[] = [];
let deserializeMainCalled = false;
function startupSnapshotSetDeserializeMainFunction(
fn: SnapshotCallback,
data?: unknown,
) {
validateFunction(fn, "callback");
if (deserializeMainCalled) {
throw new Error(
"v8.startupSnapshot.setDeserializeMainFunction() can only be called once.",
);
}
deserializeMainCalled = true;
fn(data);
}
function startupSnapshotAddSerializeCallback(
fn: SnapshotCallback,
data?: unknown,
) {
validateFunction(fn, "callback");
ArrayPrototypePush(serializeCallbacks, { fn, data });
}
function startupSnapshotAddDeserializeCallback(
fn: SnapshotCallback,
data?: unknown,View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Register the main function in exactly one module and delete the duplicate call.
- For repeatable setup use addSerializeCallback/addDeserializeCallback, which accept multiple registrations.
- Guard registration behind a module-level flag when re-execution is possible (HMR, tests).
Example fix
// before — two entrypoints each register a main startupSnapshot.setDeserializeMainFunction(mainA); startupSnapshot.setDeserializeMainFunction(mainB); // Error: can only be called once // after — one main that dispatches, plus callbacks for the rest startupSnapshot.setDeserializeMainFunction(mainA); startupSnapshot.addDeserializeCallback(() => mainB());
Defensive patterns
Strategy: validation
Validate before calling
let mainRegistered = false;
export function registerSnapshotMain(fn, data) {
if (mainRegistered) return; // idempotent across HMR/test re-imports
mainRegistered = true;
v8.startupSnapshot.setDeserializeMainFunction(fn, data);
} Prevention
- Keep snapshot entry registration in a single dedicated file.
- Prefer addDeserializeCallback for anything additive or optional.
- Note Deno's polyfill reports isBuildingSnapshot() === false — registration here is bookkeeping, so design entry logic accordingly.
When it happens
Trigger: Calling setDeserializeMainFunction twice in the same isolate — module top-level registration re-executed during dev reload/HMR, or two modules both trying to own the snapshot entry point.
Common situations: Snapshot-building scripts refactored so registration moved into a shared helper invoked from two files; test harnesses importing the snapshot script twice; boilerplate duplicated between entry points.
Related errors
- buffer must be a TypedArray or a DataView
- source must be a TypedArray or a DataView
- ReadRawBytes() failed
- The "${name}" argument must be of type function. Received ${
- Failed to initialize V8 platform (could not start worker thr
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/16a77847df0d4332.
Report an issue: GitHub.