denoland/deno · error · Deno.errors.Http
Already upgraded
Error message
Already upgraded
What it means
In Deno.serve, an incoming request can be upgraded at most once. _throwIfUpgraded is the guard invoked by the public upgrade helpers (e.g. Deno.upgradeWebSocket) before performing an upgrade; if the internal flag #upgraded is already set, it throws Deno.errors.Http ('Already upgraded'), because the connection has already been converted to another protocol.
Solutions
- Upgrade exactly once per request and return the expected handler result immediately after
- Branch on request.headers.get('upgrade') first so only one upgrade path can run
- Track upgrade state in your own handler scope (e.g. let upgraded = false) around shared helpers
Example fix
// before
const { socket, response } = Deno.upgradeWebSocket(request);
// later, another branch:
Deno.upgradeWebSocket(request); // Deno.errors.Http: Already upgraded
// after
let upgraded = false;
if (!upgraded && request.headers.get("upgrade")?.toLowerCase() === "websocket") {
upgraded = true;
const { socket, response } = Deno.upgradeWebSocket(request);
return response;
} Defensive patterns
Strategy: try-catch
Try / catch
let upgraded = false;
function upgradeOnce(request) {
if (upgraded) return null;
upgraded = true;
try {
return Deno.upgradeWebSocket(request);
} catch (err) {
if (err instanceof Deno.errors.Http) return null; // Already upgraded/closed
throw err;
}
} Prevention
- Perform at most one upgrade per request; branch on the upgrade header first
- Return immediately after a successful upgrade
- Wrap optional upgrade attempts in code that tolerates Deno.errors.Http
When it happens
Trigger: Calling Deno.upgradeWebSocket(request) a second time after a successful first upgrade; a handler that upgrades and then another code path (retry wrapper, fallthrough branch) attempts the upgrade again.
Common situations: Handlers with multiple branches that both upgrade; middleware that re-invokes the handler; upgrading both WebSocket and HTTP on the same request; retry loops around the handler call.
Related errors
- Already closed
- Invalid Header: 'upgrade' header must contain 'websocket'
- Deno.serve: request.signal aborts on successful responses…
- ${error}
- Invalid Header: 'connection' header must contain 'Upgrade'
AI-assisted analysis of denoland/deno@abd22074e4 (2026-08-20).
Data as JSON: /api/errors/5cea0416f54b669e.
Report an issue: GitHub.
Appendix: source
Thrown at ext/http/00_serve.ts:239
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 abd22074e4)