{"record":{"id":"3638e1383883c0eb","repo":"nodejs/node","slug":"etag-mismatch","errorCode":null,"errorMessage":"ETag mismatch","messagePattern":"ETag mismatch","errorType":"exception","errorClass":"RequestRetryError","httpStatus":null,"severity":"error","filePath":"deps/undici/src/lib/handler/retry-handler.js","lineNumber":292,"sourceCode":"          headers,\n          data: { count: this.retryCount }\n        })\n      }\n\n      const contentRange = parseRangeHeader(headers['content-range'])\n      // If no content range\n      if (!contentRange) {\n        // We always throw here as we want to indicate that we entred unexpected path\n        throw new RequestRetryError('Content-Range mismatch', statusCode, {\n          headers,\n          data: { count: this.retryCount }\n        })\n      }\n\n      // Let's start with a weak etag check\n      if (this.etag != null && this.etag !== headers.etag) {\n        // We always throw here as we want to indicate that we entred unexpected path\n        throw new RequestRetryError('ETag mismatch', statusCode, {\n          headers,\n          data: { count: this.retryCount }\n        })\n      }\n\n      validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount)\n\n      const { start, size, end = size ? size - 1 : null } = contentRange\n\n      assert(this.start === start, 'content-range mismatch')\n      assert(this.end == null || this.end === end, 'content-range mismatch')\n\n      return\n    }\n\n    if (this.end == null) {\n      if (statusCode === 206) {\n        // First time we receive 206","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/deps/undici/src/lib/handler/retry-handler.js#L274-L310","documentation":"Thrown by RetryHandler.onResponseStart during a retry resume when the original response had a strong ETag recorded but the resumed response carries a different ETag. The retry handler checkpoints the body by byte offset and uses If-Match to guarantee the same resource; an ETag change means the resource changed under us, so resuming would corrupt the downloaded file. This is a safety guard, not a bug.","triggerScenarios":"A ranged retry resume request where the origin object was overwritten between the original and the resume response, producing a new ETag; the If-Match check is bypassed or the server ignores If-Match and returns the new representation anyway.","commonSituations":"Mutable objects in object storage overwritten mid-download; CDN cache misses returning a newer version; deployments happening during a long download; servers that do not honor If-Match.","solutions":["Restart the download from byte 0 when the resource is mutable (do not resume).","Pin the version via a strong ETag/If-Match that the server honors, or via an immutable content-addressed URL.","Disable transparent retry-resume for endpoints whose content can change between requests."],"exampleFix":"// before (resume across a version change)\nclient = client.compose(interceptors.retry())\nawait client.request({ path: '/mutable-file' })\n\n// after\n// fetch atomically; on ETag mismatch restart from the beginning\nasync function stableDownload(path) {\n  let etag, buf = []\n  while (true) {\n    const res = await agent.request({ path, headers: etag ? { 'if-match': etag } : {} })\n    if (res.statusCode === 412) { buf = []; continue }\n    etag = res.headers.etag\n    buf.push(await res.body.arrayBuffer())\n    return Buffer.concat(buf.map(Buffer.from))\n  }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await retryClient.request({ path }) } catch (e) { if (e.code === 'UND_ERR_REQ_RETRY' && /ETag mismatch/.test(e.message)) { /* resource changed: restart from 0 */ await baseClient.request({ path }) } else throw e }","preventionTips":["Use immutable/content-addressed URLs for mutable resources.","Pin versions with a strong ETag + If-Match honored by the origin.","Restart downloads on ETag change rather than resuming."],"tags":["undici","http","retry","etag","range","resume","data-integrity"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}