{"record":{"id":"5cea0416f54b669e","repo":"denoland/deno","slug":"already-upgraded","errorCode":null,"errorMessage":"Already upgraded","messagePattern":"Already upgraded","errorType":"exception","errorClass":"Deno.errors.Http","httpStatus":null,"severity":"error","filePath":"ext/http/00_serve.ts","lineNumber":239,"sourceCode":"      if (success && this.#signalAccessed && !legacyAbortWarned) {\n        legacyAbortWarned = true;\n        // deno-lint-ignore no-console\n        console.warn(\n          \"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\",\n        );\n      }\n      abortRequest(this.request);\n    }\n    this.#external = null;\n  }\n\n  get [_upgraded]() {\n    return this.#upgraded;\n  }\n\n  _throwIfUpgraded() {\n    if (this.#upgraded) {\n      throw new Deno.errors.Http(\"Already upgraded\");\n    }\n  }\n\n  _wantsUpgrade(upgradeType) {\n    if (this.#upgraded) {\n      throw new Deno.errors.Http(\"Already upgraded\");\n    }\n    if (this.#external === null) {\n      throw new Deno.errors.Http(\"Already closed\");\n    }\n\n    if (upgradeType == \"upgradeWebSocket\") {\n      const external = this.#external;\n\n      this.url();\n      this.headerList;\n      this.remoteAddr;\n      this.close();","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/denoland/deno/blob/abd22074e47c6a5cd14e9e4e84743f084aa5a575/ext/http/00_serve.ts#L221-L257","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst { socket, response } = Deno.upgradeWebSocket(request);\n// later, another branch:\nDeno.upgradeWebSocket(request); // Deno.errors.Http: Already upgraded\n\n// after\nlet upgraded = false;\nif (!upgraded && request.headers.get(\"upgrade\")?.toLowerCase() === \"websocket\") {\n  upgraded = true;\n  const { socket, response } = Deno.upgradeWebSocket(request);\n  return response;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let upgraded = false;\nfunction upgradeOnce(request) {\n  if (upgraded) return null;\n  upgraded = true;\n  try {\n    return Deno.upgradeWebSocket(request);\n  } catch (err) {\n    if (err instanceof Deno.errors.Http) return null; // Already upgraded/closed\n    throw err;\n  }\n}","preventionTips":["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"],"tags":["http","serve","upgrade","websocket"],"backgroundTag":"connection-already-upgraded","analyzedSha":"abd22074e47c6a5cd14e9e4e84743f084aa5a575","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}