denoland/deno · error · ERR_CRYPTO_HASH_FINALIZED

ERR_CRYPTO_HASH_FINALIZED

ERR_CRYPTO_HASH_FINALIZED

Error message

Digest already called

What it means

Hash.update() funnels its native op results through unwrapErr(): when op_node_hash_update / op_node_hash_update_str returns false, the hash handle can no longer accept data and ERR_CRYPTO_HASH_FINALIZED ('Digest already called') is thrown. A hash is single-shot — once digest() consumes it, update() on the same object is illegal.

Source

Thrown at ext/node/polyfills/internal/crypto/hash.ts:73

  isArrayBufferView,
} = core.loadExtScript("ext:deno_node/internal/util/types.ts");

const {
  FunctionPrototypeCall,
  ObjectPrototypeIsPrototypeOf,
  ObjectSetPrototypeOf,
  ReflectApply,
  SafeArrayIterator,
  StringFromCharCode,
  StringPrototypeToLowerCase,
  Symbol,
  TypedArrayPrototypeGetByteLength,
  Uint8Array,
  Uint8ArrayPrototype,
} = primordials;

function unwrapErr(ok: boolean) {
  if (!ok) throw new ERR_CRYPTO_HASH_FINALIZED();
}

const kHandle = Symbol("kHandle");
const kFinalized = Symbol("kFinalized");

let warnedShakeOutputLength = false;

function Hash(
  algorithm: string | Hasher,
  options?: { outputLength?: number },
): Hash {
  if (!ObjectPrototypeIsPrototypeOf(Hash.prototype, this)) {
    return new Hash(algorithm, options);
  }
  const isCopy = ObjectPrototypeIsPrototypeOf(Hasher.prototype, algorithm);
  if (!isCopy) {
    validateString(algorithm, "algorithm");
  }

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Create a fresh crypto.createHash(algorithm) for every digest you need — Hash objects are cheap and not reusable
  2. If you need the running state later, call h.copy() BEFORE digest() and digest the copy: const h2 = h.copy(); h2.digest('hex')
  3. Remove intermediate digest() calls used for logging, or route them through a copy

Example fix

// before
const h = crypto.createHash('sha256');
h.update(chunk1);
console.log(h.digest('hex')); // finalized
h.update(chunk2); // throws

// after
const h = crypto.createHash('sha256');
h.update(chunk1);
const h2 = h.copy();
console.log(h2.digest('hex')); // digest the copy
h.update(chunk2);
Defensive patterns

Strategy: validation

Validate before calling

// Own the lifecycle: one Hash per digest, copy before consuming.
class SafeHash {
  #h = crypto.createHash('sha256');
  #done = false;
  update(data: string | Buffer) {
    if (this.#done) throw new Error('SafeHash already digested');
    this.#h.update(data);
    return this;
  }
  snapshot() { return this.#done ? null : this.#h.copy(); }
  digest(enc?: string) { this.#done = true; return this.#h.digest(enc); }
}

Try / catch

catch (e) { if ((e as NodeJS.ErrnoException).code === 'ERR_CRYPTO_HASH_FINALIZED') { /* start a fresh createHash and re-feed the buffered input */ } throw e; }

Prevention

When it happens

Trigger: const h = crypto.createHash('sha256'); h.digest('hex'); h.update('more'); — any update after digest. Common in streams when a final chunk arrives after the hash object was finalized, or when a helper digests early for logging and the caller keeps updating.

Common situations: Reusing one Hash instance across requests or loop iterations; middleware that computes an intermediate digest for debug logging; piping a stream where 'end' handling digests the hash before a late write.

Related errors


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