denoland/deno · error · ERR_WASI_ALREADY_STARTED

ERR_WASI_ALREADY_STARTED

ERR_WASI_ALREADY_STARTED

Error message

WASI instance has already started

What it means

ERR_WASI_ALREADY_STARTED is thrown by start() when this.#started is already true. The flag is a one-way latch shared by start(), initialize(), and finalizeBindings(): once any of them has bound memory, re-entering start() on the same WASI object is invalid. Each WebAssembly module run needs a fresh WASI instance, matching Node's behavior.

Source

Thrown at ext/node/polyfills/wasi.ts:688

    }
    // deno-lint-ignore deno-internal/prefer-primordials -- WebAssembly.Memory.prototype.buffer getter; no primordial equivalent
    return new Uint8Array(this.#memory.buffer);
  }

  get wasiImport() {
    return this.#wasiImport;
  }

  getImportObject() {
    if (this.#version === "unstable") {
      return { wasi_unstable: this.#wasiImport };
    }
    return { wasi_snapshot_preview1: this.#wasiImport };
  }

  start(instance?: WebAssembly.Instance): number {
    if (this.#started) {
      throw new ERR_WASI_ALREADY_STARTED();
    }

    if (instance === undefined || instance === null) {
      throw new ERR_INVALID_ARG_TYPE("instance", "object", instance);
    }
    if (typeof instance !== "object") {
      throw new ERR_INVALID_ARG_TYPE("instance", "object", instance);
    }

    const exports = instance.exports;
    if (exports === null || typeof exports !== "object") {
      throw new ERR_INVALID_ARG_TYPE("instance.exports", "object", exports);
    }

    if (typeof exports._start !== "function") {
      throw new ERR_INVALID_ARG_TYPE(
        "instance.exports._start",
        "function",

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Construct a new `new WASI({ version: 'preview1', ... })` for every module instantiation/run.
  2. If you call initialize() for reactors, do not also call start() on the same object — pick one lifecycle per instance.
  3. Remove any caching/singleton pattern around the WASI object.

Example fix

// before
const wasi = new WASI({ version: 'preview1' });
for (const mod of modules) {
  const inst = new WebAssembly.Instance(mod, wasi.getImportObject());
  wasi.start(inst); // throws on second iteration
}

// after
for (const mod of modules) {
  const wasi = new WASI({ version: 'preview1' });
  const inst = new WebAssembly.Instance(mod, wasi.getImportObject());
  wasi.start(inst);
}
Defensive patterns

Strategy: validation

Validate before calling

// One WASI per run: construct inside the runner, never outside.
function runWasi(bytes: Buffer, preopens: Record<string, string>): number {
  const wasi = new WASI({ version: 'preview1', preopens });
  const module = new WebAssembly.Module(bytes);
  const instance = new WebAssembly.Instance(module, wasi.getImportObject());
  return wasi.start(instance);
}

Try / catch

try {
  wasi.start(instance);
} catch (err) {
  if ((err as { code?: string }).code === 'ERR_WASI_ALREADY_STARTED') {
    // reuse detected: build a fresh WASI and retry once with a new instance
    const fresh = new WASI({ version: 'preview1', preopens });
    const inst = new WebAssembly.Instance(module, fresh.getImportObject());
    return fresh.start(inst);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling `wasi.start(instanceA)` then `wasi.start(instanceB)` on the same wasi object; calling initialize() after start() on the same object; calling start() on a wasi already bound via finalizeBindings() in a thread-spawn setup.

Common situations: Looping over several wasm modules while reusing one WASI instance 'for efficiency'; a task runner that caches the WASI object across requests; mixing reactor (initialize) and command (start) paths against one instance.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/aa82d04c4d3e67e0. Report an issue: GitHub.