denoland/deno · error · NodeError

ERR_HTTP2_HEADERS_SENT

ERR_HTTP2_HEADERS_SENT

Error message

Response has already been initiated.

What it means

Http2ServerResponse.removeHeader throws ERR_HTTP2_HEADERS_SENT once the response headers have been serialized onto the h2 stream (stream.headersSent). After that point the header block has already been sent; removing a header is impossible.

Source

Thrown at ext/node/polyfills/internal/http2/compat.js:657

  getHeaderNames() {
    return ObjectKeys(this[kHeaders]);
  }

  getHeaders() {
    const headers = { __proto__: null };
    return ObjectAssign(headers, this[kHeaders]);
  }

  hasHeader(name) {
    validateString(name, "name");
    name = StringPrototypeToLowerCase(StringPrototypeTrim(name));
    return ObjectHasOwn(this[kHeaders], name);
  }

  removeHeader(name) {
    validateString(name, "name");
    if (this[kStream].headersSent) {
      throw new ERR_HTTP2_HEADERS_SENT();
    }

    name = StringPrototypeToLowerCase(StringPrototypeTrim(name));

    if (name === "date") {
      this[kState].sendDate = false;

      return;
    }

    delete this[kHeaders][name];
  }

  setHeader(name, value) {
    validateString(name, "name");
    if (this[kStream].headersSent) {
      throw new ERR_HTTP2_HEADERS_SENT();
    }

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Guard every removal: if (!res.headersSent) res.removeHeader(name)
  2. Reorder middleware so all header mutations happen before the first write/end
  3. If headers already went out, the only options are finishing the response or destroying the stream — fix the ordering instead

Example fix

// before
try { res.end(body); } finally { res.removeHeader("x-debug"); } // throws

// after
res.removeHeader("x-debug"); // mutate before any write
res.end(body);
Defensive patterns

Strategy: validation

Validate before calling

function safeRemoveHeader(res, name) {
  if (!res.headersSent) res.removeHeader(name);
}

Type guard

function canMutateHeaders(res: http2.Http2ServerResponse): boolean {
  return !res.headersSent;
}

Try / catch

try {
  res.removeHeader(name);
} catch (err) {
  if (err.code === "ERR_HTTP2_HEADERS_SENT") return; // too late; ignore
  throw err;
}

Prevention

When it happens

Trigger: Calling res.removeHeader("content-length") after res.write()/res.end()/writeHead() has begun the response; error handlers trying to strip headers after the body started; cleanup code in a finally block that runs after streaming starts.

Common situations: Late route errors after body streaming began; security filters removing internal headers (x-debug, x-internal-*) that run too late in the middleware chain; logging/observability middleware mutating headers on 'finish'.

Related errors


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