denoland/deno · error · Error

ERR_INVALID_STATE

ERR_INVALID_STATE

Error message

Invalid state: Cannot validate on a detached buffer

What it means

Buffer.isUtf8(input) checks whether bytes form valid UTF-8. When input is a TypedArray whose underlying ArrayBuffer has been detached (moved away via a transfer), the data no longer exists and validation is impossible, so ERR_INVALID_STATE is thrown instead of returning a boolean. Detaching happens with structuredClone(..., { transfer }) or Worker/MessagePort postMessage transfer lists.

Source

Thrown at ext/node/polyfills/internal/buffer.mjs:3166

}

// deno-lint-ignore camelcase
function writeU_Int24LE(buf, value, offset, min, max) {
  value = +value;
  checkInt(value, min, max, buf, offset, 2);

  buf[offset++] = value;
  value = value >>> 8;
  buf[offset++] = value;
  value = value >>> 8;
  buf[offset++] = value;
  return offset;
}

function isUtf8(input) {
  if (isTypedArray(input)) {
    if (isDetachedBuffer(TypedArrayPrototypeGetBuffer(input))) {
      throw new ERR_INVALID_STATE("Cannot validate on a detached buffer");
    }
    return op_is_utf8(input);
  }

  if (isAnyArrayBuffer(input)) {
    if (isDetachedBuffer(input)) {
      throw new ERR_INVALID_STATE("Cannot validate on a detached buffer");
    }
    return op_is_utf8(new Uint8Array(input));
  }

  throw new codes.ERR_INVALID_ARG_TYPE("input", [
    "ArrayBuffer",
    "Buffer",
    "TypedArray",
  ], input);
}

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Validate only on the receiving side of the transfer - the sender's view is dead after transfer
  2. Guard first: if (view.buffer.detached) skip - ArrayBuffer.prototype.detached is available in Deno
  3. Drop the transfer list when the data is still needed locally: structuredClone(view) copies instead of moving
  4. Keep a copy before transferring and validate the copy

Example fix

// before
const view = new Uint8Array([0xf0]);
structuredClone(view, { transfer: [view.buffer] });
Buffer.isUtf8(view); // ERR_INVALID_STATE: Cannot validate on a detached buffer

// after
const clone = structuredClone(view); // keep local copy
structuredClone(view, { transfer: [view.buffer] });
Buffer.isUtf8(clone);
Defensive patterns

Strategy: type-guard

Validate before calling

function isUtf8Safe(view) {
  if (!(view instanceof Uint8Array)) throw new TypeError('expected Uint8Array');
  if (view.buffer.detached) return null; // data moved elsewhere
  return Buffer.isUtf8(view);
}

Type guard

const isLiveView = (v) => v instanceof Uint8Array && !v.buffer.detached;

Try / catch

try {
  ok = Buffer.isUtf8(view);
} catch (e) {
  if (e?.code === 'ERR_INVALID_STATE') { ok = null; /* buffer was transferred */ }
  else throw e;
}

Prevention

When it happens

Trigger: const v = new Uint8Array(n); structuredClone(v, { transfer: [v.buffer] }); Buffer.isUtf8(v); - also after worker.postMessage(buf, [buf.buffer]) and then re-validating on the sending side.

Common situations: Zero-copy pipelines that transfer buffers to workers and keep validating the now-empty local view; views cached across async boundaries where another code path transferred the buffer; ownership bugs where both sides assume they still hold the data.

Related errors


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