{"record":{"id":"2d90a3036614a59b","repo":"denoland/deno","slug":"response-body-is-already-used","errorCode":null,"errorMessage":"Response body is already used","messagePattern":"Response body is already used","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/cache/01_cache.js","lineNumber":176,"sourceCode":"    // Step 6.\n    if (innerResponse.status === 206) {\n      throw new TypeError(\"Response status must not be 206\");\n    }\n    // Step 7.\n    const varyHeader = getHeader(innerResponse.headerList, \"vary\");\n    if (varyHeader) {\n      const fieldValues = StringPrototypeSplit(varyHeader, \",\");\n      for (let i = 0; i < fieldValues.length; ++i) {\n        const field = fieldValues[i];\n        if (StringPrototypeTrim(field) === \"*\") {\n          throw new TypeError(\"Vary header must not contain '*'\");\n        }\n      }\n    }\n\n    // Step 8.\n    if (innerResponse.body !== null && innerResponse.body.unusable()) {\n      throw new TypeError(\"Response body is already used\");\n    }\n\n    const stream = innerResponse.body?.stream;\n    let rid = null;\n    if (stream) {\n      const resourceBacking = getReadableStreamResourceBacking(\n        innerResponse.body?.stream,\n      );\n      if (resourceBacking) {\n        rid = resourceBacking.rid;\n      } else {\n        rid = resourceForReadableStream(stream, innerResponse.body?.length);\n      }\n    }\n\n    // Remove fragment from request URL before put.\n    reqUrl.hash = \"\";\n","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/denoland/deno/blob/89f33cbef296a2b287f323d42de54c871fa69c77/ext/cache/01_cache.js#L158-L194","documentation":"Step 8 of Cache.put(): the cache consumes the response body itself, so the body must be unused. If the stream is already read or locked (innerResponse.body.unusable()), put() throws 'Response body is already used'.","triggerScenarios":"await response.text()/.json()/.arrayBuffer() before cache.put(request, response); passing a response whose body was locked or read by another consumer.","commonSituations":"Logging or inspecting a fetched response and then trying to cache the same object; caching after forwarding the body to another reader.","solutions":["Clone before reading: await cache.put(req, response.clone()), then read the original.","Cache first and read via cache.match afterwards.","Build a fresh Response from data you already have: new Response(text, init)."],"exampleFix":"// before\nconst text = await res.text();\nawait cache.put(req, res); // throws: body already used\n// after\nawait cache.put(req, res.clone());\nconst text = await res.text();","handlingStrategy":"validation","validationCode":"if (!response.bodyUsed) {\n  await cache.put(request, response.clone());\n  return response;\n}\nthrow new Error('cannot cache a response whose body was read');","typeGuard":null,"tryCatchPattern":"try { await cache.put(req, res); } catch (e) { if (e instanceof TypeError && /already used/.test(e.message)) { /* re-fetch or rebuild from stored text */ } else throw e; }","preventionTips":["Clone at the point of caching: cache.put(req, res.clone()) — never read and cache the same object.","Check response.bodyUsed as a cheap pre-condition before put().","Structure fetch wrappers as: clone for cache, return original to caller."],"tags":["cache","response-body","stream","fetch"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}