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
- Emit informational responses as the very first thing in the request lifecycle (a before-handler hook)
- Guard every call with if (!res.headersSent) before writeInformation
- 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
- Register early-hints emission before any body-writing middleware
- Check res.headersSent anywhere response state is uncertain
- Keep informational responses in one place at the front of the request pipeline
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
- ERR_HTTP_SOCKET_ASSIGNED
- ERR_INVALID_CHAR
- ERR_OUT_OF_RANGE
- ERR_HTTP_BODY_NOT_ALLOWED
- ERR_HTTP_CONTENT_LENGTH_MISMATCH
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)