denoland/deno · error · NodeError

ERR_HTTP_HEADERS_SENT

ERR_HTTP_HEADERS_SENT

Error message

Cannot write headers after they are sent to the client

What it means

ServerResponse.prototype.writeInformation (the path behind 1xx informational responses such as 102 Processing and 103 Early Hints) throws ERR_HTTP_HEADERS_SENT when this._header is already truthy: the final response's headers were already stored by an earlier writeHead or an implicit header flush. Informational responses are only legal before the final response begins, so the polyfill refuses to send them afterwards, matching Node.

Source

Thrown at ext/node/polyfills/_http_server.js:424

  this.socket = null;
};

ServerResponse.prototype.writeContinue = function writeContinue(cb) {
  this._writeRaw("HTTP/1.1 100 Continue\r\n\r\n", "ascii", cb);
  this._sent100 = true;
};

ServerResponse.prototype.writeProcessing = function writeProcessing(cb) {
  this._writeRaw("HTTP/1.1 102 Processing\r\n\r\n", "ascii", cb);
};

ServerResponse.prototype.writeInformation = function writeInformation(
  statusCode,
  headers,
  cb,
) {
  if (this._header) {
    throw new ERR_HTTP_HEADERS_SENT("write");
  }

  if (typeof headers === "function") {
    cb = headers;
    headers = undefined;
  }

  validateInteger(statusCode, "statusCode", 100, 199);

  let head = `HTTP/1.1 ${statusCode} ${
    STATUS_CODES[statusCode] || "unknown"
  }\r\n`;

  if (headers !== null && headers !== undefined) {
    const keys = ObjectKeys(headers);
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i];
      validateHeaderName(key);

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Emit informational (1xx) responses before any writeHead/write/end on the final response
  2. Guard every call with if (!res.headersSent && !res._header)
  3. Fix middleware ordering so hints run at the top of the pipeline
  4. Once headers are sent, drop the hint - late 1xx carries no value to the client anyway

Example fix

// before
res.writeHead(200, { 'content-type': 'text/html' });
res.writeInformation(103, { link: '</style.css>' }); // throws

// after
if (!res.headersSent && !res._header) {
  res.writeInformation(103, { link: '</style.css>' });
}
res.writeHead(200, { 'content-type': 'text/html' });
Defensive patterns

Strategy: validation

Validate before calling

function canSendInformational(res) {
  return !res.headersSent && !res._header;
}
if (canSendInformational(res)) {
  res.writeInformation(103, { 'link': '</style.css>' });
}

Try / catch

try {
  res.writeInformation(102);
} catch (e) {
  if (e.code === 'ERR_HTTP_HEADERS_SENT') {
    // final headers already flushed: skip the informational response
  } else throw e;
}

Prevention

When it happens

Trigger: Calling res.writeInformation(status, headers) or res.writeProcessing() after res.writeHead(), res.end(), or a res.write() that flushed the implicit header; early-hints middleware that runs after the route handler already started the response.

Common situations: Early-hints middleware ordered after the handler; streaming handlers that emit 102 Processing after the body started; retry/progress wrappers that report status too late in the request lifecycle.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/3274ed30cbe8a7a0. Report an issue: GitHub.