denoland/deno · error · Error

ERR_HTTP_HEADERS_SENT

ERR_HTTP_HEADERS_SENT

Error message

Cannot write headers after they are sent to the client

What it means

ERR_HTTP_HEADERS_SENT from ServerResponse.writeInformation(): 1xx informational responses (such as 103 Early Hints) are only valid before the final response headers exist. Once this._header is set — by writeHead(), a first write() that flushes implicit headers, or end() — writeInformation throws 'Cannot write headers after they are sent to the client'.

Solutions

  1. Emit informational responses as the very first thing in the request lifecycle (a before-handler hook)
  2. Guard every call with if (!res.headersSent) before writeInformation
  3. If headers are already sent, skip the hint — it can no longer help the client

Example fix

// before
res.writeHead(200, headers);
res.writeInformation(103, { link: '</app.js>; rel=preload' }); // throws

// after
if (!res.headersSent) res.writeInformation(103, { link: '</app.js>; rel=preload' });
res.writeHead(200, headers);
Defensive patterns

Strategy: validation

Validate before calling

if (!res.headersSent && !res.writableEnded) {
  res.writeInformation(103, hints);
}

Type guard

const canSendInformational = (res) => !res.headersSent && !res.writableEnded;

Try / catch

try {
  res.writeInformation(103, hints);
} catch (e) {
  if (e.code === 'ERR_HTTP_HEADERS_SENT') return; // too late for hints
  throw e;
}

Prevention

When it happens

Trigger: res.writeInformation(103, {...}) called after res.writeHead(200), after the first res.write() already forced an implicit header flush, or after res.end().

Common situations: Early-Hints middleware that runs after the handler already streamed a response; async frameworks where a body write wins the race against the hints hook; double-invoked handlers during retries or error re-entry.

Related errors


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

Appendix: 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 9ad36f7a2c)