{"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":224,"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":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/denoland/deno/blob/89f33cbef296a2b287f323d42de54c871fa69c77/ext/http/00_serve.ts#L206-L242","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst { socket, response } = info.upgradeWebSocket(req);\nreturn new Response(\"upgraded\"); // Deno.errors.Http: Already upgraded\n\n// after\nif (req.headers.get(\"upgrade\") === \"websocket\") {\n  return info.upgradeWebSocket(req).response; // exclusive branch\n}\nreturn new Response(\"normal\");","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await respond(ctx, response);\n} catch (err) {\n  if (err instanceof Deno.errors.Http && err.message === \"Already upgraded\") {\n    // request was upgraded; skip response handling silently\n  } else throw err;\n}","preventionTips":["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'"],"tags":["http","serve","websocket","upgrade"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}