denoland/deno · error · Deno.errors.Http
Already upgraded
Error message
Already upgraded
What it means
Once a request handled by Deno.serve has been upgraded (e.g. to a WebSocket), its request context is finalized and cannot produce a normal HTTP response. The _throwIfUpgraded guard rejects response-related operations with a Deno.errors.Http error. Only one terminal action — upgrade or respond — is allowed per request.
Source
Thrown at ext/http/00_serve.ts:224
if (success && this.#signalAccessed && !legacyAbortWarned) {
legacyAbortWarned = true;
// deno-lint-ignore no-console
console.warn(
"Deno.serve: request.signal aborts on successful responses (legacy behavior). To detect when a request has been fully delivered use the `completed` promise on the handler's info argument. Move cleanup to the handler's return path, or opt in to the new behavior with --unstable-no-legacy-abort. See https://docs.deno.com/go/unstable-no-legacy-abort",
);
}
abortRequest(this.request);
}
this.#external = null;
}
get [_upgraded]() {
return this.#upgraded;
}
_throwIfUpgraded() {
if (this.#upgraded) {
throw new Deno.errors.Http("Already upgraded");
}
}
_wantsUpgrade(upgradeType) {
if (this.#upgraded) {
throw new Deno.errors.Http("Already upgraded");
}
if (this.#external === null) {
throw new Deno.errors.Http("Already closed");
}
if (upgradeType == "upgradeWebSocket") {
const external = this.#external;
this.url();
this.headerList;
this.remoteAddr;
this.close();View on GitHub (pinned to 89f33cbef2)
Solutions
- Return immediately after performing the upgrade; make upgrade branches exclusive
- Restructure the handler so upgrade and respond paths are early-returning if/else branches
- Catch Deno.errors.Http in middleware that may run after an upgrade and treat it as end-of-processing
Example fix
// before
const { socket, response } = info.upgradeWebSocket(req);
return new Response("upgraded"); // Deno.errors.Http: Already upgraded
// after
if (req.headers.get("upgrade") === "websocket") {
return info.upgradeWebSocket(req).response; // exclusive branch
}
return new Response("normal"); Defensive patterns
Strategy: try-catch
Try / catch
try {
await respond(ctx, response);
} catch (err) {
if (err instanceof Deno.errors.Http && err.message === "Already upgraded") {
// request was upgraded; skip response handling silently
} else throw err;
} Prevention
- Make upgrade and respond exclusive early-return branches per request
- Return immediately after a successful upgrade
- In middleware, treat Deno.errors.Http as 'already handled downstream'
When it happens
Trigger: Calling the response path (e.g. returning/committing a Response via APIs that route through _throwIfUpgraded) after a successful upgradeWebSocket on the same request within a serve handler.
Common situations: Handlers that upgrade and then fall through to a return statement with a Response; middleware that writes a response after an inner handler already upgraded; retry/fallback logic that tries to respond normally after a failed upgrade succeeded partially.
Related errors
- Already closed
- Request closed
- Invalid response
- Return value from serve handler must be a response or a prom
- Return value from serve handler must be a Response construct
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/5cea0416f54b669e.
Report an issue: GitHub.