denoland/deno · error · DOMException

DataCloneError

DataCloneError

Error message

Cannot clone object of unsupported type.

What it means

Worker.postMessage serializes with structured clone. On the no-transfer fast path the polyfill first checks an isUncloneable marker (honoring markAsUncloneable) so values the V8 serializer would silently flatten to {} — URL objects, marked instances — instead throw DOMException 'Cannot clone object of unsupported type.' with DataCloneError, matching the web MessagePort path and Node's behavior.

Source

Thrown at ext/node/polyfills/worker_threads.ts:792

      }
    }
  };

  postMessage(message, transferOrOptions = { __proto__: null }) {
    const prefix = "Failed to execute 'postMessage' on 'MessagePort'";
    webidl.requiredArguments(arguments.length, 1, prefix);
    if (this.#status !== "RUNNING") return;
    // Fast path: no transferables
    if (
      transferOrOptions === undefined ||
      transferOrOptions === null ||
      (arguments.length <= 1)
    ) {
      // Reject non-serializable values (e.g. URL) and per-instance
      // markAsUncloneable values before V8's serializer silently turns them
      // into `{}`, matching the web MessagePort path and Node's behavior.
      if (isUncloneable(message)) {
        throw new DOMException(
          "Cannot clone object of unsupported type.",
          "DataCloneError",
        );
      }
      op_host_post_message_raw(
        this.#id,
        serializeMessageData(message),
      );
      return;
    }
    message = webidl.converters.any(message);
    let options;
    if (
      webidl.type(transferOrOptions) === "Object" &&
      transferOrOptions !== undefined &&
      transferOrOptions[SymbolIterator] !== undefined
    ) {
      const transfer = webidl.converters["sequence<object>"](

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Convert to clone-safe data first: url.href instead of the URL object, plain objects instead of class instances.
  2. Add a toJSON()/toPlain() conversion on your classes, or explicitly JSON round-trip when the data allows it.
  3. For shared mutable state use SharedArrayBuffer instead of posting objects.
  4. Respect markAsUncloneable — if a class is marked, pass its fields, not the instance.

Example fix

// before
worker.postMessage({ endpoint: new URL('https://api.example.com/v1') });

// after
worker.postMessage({ endpoint: 'https://api.example.com/v1' }); // strings clone cleanly
Defensive patterns

Strategy: type-guard

Validate before calling

function isCloneable(v: unknown, depth = 0): boolean {
  if (v == null) return true;
  const t = typeof v;
  if (t !== 'object') return t !== 'function' && t !== 'symbol';
  if (v instanceof URL) return false; // known uncloneable
  if (v instanceof Date || v instanceof RegExp || v instanceof ArrayBuffer || v instanceof SharedArrayBuffer) return true;
  if (depth > 8) return false;
  const kids = Array.isArray(v) ? v : (v instanceof Map || v instanceof Set) ? [...v] : Object.values(v as object);
  return kids.every((x) => isCloneable(x, depth + 1));
}

if (!isCloneable(msg)) throw new TypeError('message is not structured-clone safe');
worker.postMessage(msg);

Type guard

Use isCloneable from validationCode as a deep guard: if (!isCloneable(msg)) msg = toPlainObject(msg);

Try / catch

try { worker.postMessage(msg); } catch (e) { if (e?.name === 'DataCloneError') { /* re-send as serialized primitives, e.g. JSON round-trip */ } else throw e; }

Prevention

When it happens

Trigger: worker.postMessage(new URL('https://x')) or posting an instance registered via markAsUncloneable(); functions and other inherently non-cloneable values hit the same rejection.

Common situations: Sharing parsed URL or class-instance objects between threads and expecting JSON-like behavior; libraries that mark handles uncloneable to prevent silent data loss; values that otherwise 'arrive as empty object'.

Related errors


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