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
- Validate only on the receiving side of the transfer - the sender's view is dead after transfer
- Guard first: if (view.buffer.detached) skip - ArrayBuffer.prototype.detached is available in Deno
- Drop the transfer list when the data is still needed locally: structuredClone(view) copies instead of moving
- 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
- Validate before transferring, or only on the receiving side
- Check the detached property whenever a view crosses a transfer boundary
- Model buffer ownership explicitly - exactly one side reads after handoff
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
- ERR_STRING_TOO_LONG
- Buffer size must be a multiple of 16-bits
- ERR_INVALID_ARG_VALUE
- Unexpected 'fn' field in options, bench function is already
- ERR_INVALID_ARG_VALUE
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/663690a015671e19.
Report an issue: GitHub.