{"record":{"id":"780744e9e5f677f7","repo":"nexu-io/open-design","slug":"cloudflare-reported-an-unknown-asset-hash-hash","errorCode":null,"errorMessage":"Cloudflare reported an unknown asset hash: ${hash}","messagePattern":"Cloudflare reported an unknown asset hash: (.+?)","errorType":"http","errorClass":"DeployError","httpStatus":502,"severity":"error","filePath":"apps/daemon/src/deploy.ts","lineNumber":1073,"sourceCode":"        `Cloudflare Pages assets must be ${formatMib(CLOUDFLARE_PAGES_ASSET_MAX_BYTES)} or smaller: ${file.file} is ${formatMib(data.length)}.`,\n        400,\n      );\n    }\n    const hash = cloudflarePagesAssetHash({ ...file, data });\n    if (!uniqueFiles.has(hash)) {\n      uniqueFiles.set(hash, {\n        hash,\n        data,\n        contentType: file.contentType || 'application/octet-stream',\n      });\n    }\n  }\n  const hashes = Array.from(uniqueFiles.keys());\n  const missing = await cloudflarePagesMissingAssetHashes(uploadToken, hashes);\n  if (missing.length > 0) {\n    const missingFiles = missing.map((hash) => {\n      const file = uniqueFiles.get(hash);\n      if (!file) throw new DeployError(`Cloudflare reported an unknown asset hash: ${hash}`, 502);\n      return {\n        ...file,\n        hash,\n      };\n    });\n\n    for (const batch of chunkCloudflarePagesAssetUploads(missingFiles)) {\n      const payload = batch.map((file) => ({\n        key: file.hash,\n        value: file.data.toString('base64'),\n        metadata: {\n          contentType: file.contentType,\n        },\n        base64: true,\n      }));\n      const uploadResp = await fetch(`${CLOUDFLARE_API}/pages/assets/upload`, {\n        method: 'POST',\n        headers: cloudflareAssetHeaders(uploadToken, { 'Content-Type': 'application/json' }),","sourceCodeStart":1055,"sourceCodeEnd":1091,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/deploy.ts#L1055-L1091","documentation":"After the daemon asks Cloudflare which asset hashes are missing (cloudflarePagesMissingAssetHashes), Cloudflare returns a subset of hashes it wants uploaded. If Cloudflare returns a hash the daemon never proposed, the response is inconsistent with the request, so the daemon aborts with status 502 rather than upload data for an unknown key.","triggerScenarios":"Cloudflare's Pages direct-upload API returns a missing-hash set containing a hash that is not in the locally computed uniqueFiles map. Indicates an upstream inconsistency, a hash-calculation divergence, or a race where the asset store mutated mid-deploy.","commonSituations":"Transient upstream API inconsistency during concurrent deployments to the same project; extremely rare hash collision; a Cloudflare-side bug in the missing-hashes endpoint.","solutions":["Retry the deploy after a short wait; transient upstream inconsistency usually clears.","If it persists, deploy to a fresh Cloudflare Pages project name to bypass the suspect asset store state.","If reproducible, capture the deploy payload and report to Cloudflare."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"async function deployWithRetry(token: string, files: DeployFile[], attempts = 3): Promise<void> {\n  for (let i = 1; i <= attempts; i++) {\n    try {\n      await uploadCloudflarePagesAssets(token, files);\n      return;\n    } catch (err) {\n      if (err instanceof DeployError && err.status === 502 && /unknown asset hash/i.test(err.message) && i < attempts) {\n        await new Promise((r) => setTimeout(r, 1000 * i));\n        continue;\n      }\n      throw err;\n    }\n  }\n}","preventionTips":["Avoid concurrent deployments to the same Cloudflare Pages project, which can mutate the asset store mid-deploy.","Treat 502 'unknown asset hash' as transient and retry with backoff before surfacing to the user.","If the error persists across retries, redeploy under a fresh project name to bypass suspect asset-store state."],"tags":["cloudflare","deploy","upstream","retry"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}