{"record":{"id":"9f992ebd581f5577","repo":"anomalyco/sst","slug":"hash-s-not-found-in-manifest","errorCode":null,"errorMessage":"hash %s not found in manifest","messagePattern":"hash (.+?) not found in manifest","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/server/resource/cloudflare-worker-assets.go","lineNumber":230,"sourceCode":"}\n\nfunc (r *WorkerAssets) uploadAssets(manifest AssetManifest, directory, accountId, apiToken string, hashes []string, jwt string) (string, error) {\n\tvar body bytes.Buffer\n\twriter := multipart.NewWriter(&body)\n\n\tfor _, hash := range hashes {\n\t\t// Find the file path for this hash in the manifest\n\t\tvar fileKey string\n\t\tvar contentType string\n\t\tfor path, entry := range manifest {\n\t\t\tif entry.Hash == hash {\n\t\t\t\tfileKey = path\n\t\t\t\tcontentType = entry.ContentType\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif fileKey == \"\" {\n\t\t\treturn \"\", fmt.Errorf(\"hash %s not found in manifest\", hash)\n\t\t}\n\t\t\n\t\t// Read file content\n\t\tabsFilePath := filepath.Join(directory, fileKey)\n\t\tfileContent, err := os.ReadFile(absFilePath)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to read file %s: %w\", absFilePath, err)\n\t\t}\n\t\t\n\t\t// Base64 encode the file content\n\t\tencodedContent := base64.StdEncoding.EncodeToString(fileContent)\n\t\t\n\t\t// Create form field with content type header\n\t\tpart, err := writer.CreatePart(map[string][]string{\n\t\t\t\"Content-Disposition\": []string{fmt.Sprintf(`form-data; name=\"%s\"; filename=\"%s\"`, hash, hash)},\n\t\t\t\"Content-Type\":        []string{contentType},\n\t\t})\n\t\tif err != nil {","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/anomalyco/sst/blob/a0bd20f762883e72a35caccb4896c42ce5b3f707/pkg/server/resource/cloudflare-worker-assets.go#L212-L248","documentation":"During uploadAssets, a hash sent by Cloudflare in an upload bucket could not be matched back to any path in the local AssetManifest (the manifest maps file path -> {hash,size,contentType}). This means the manifest handed to the upload session is out of sync with the files that session is asking for.","triggerScenarios":"The manifest provided to handleUpload differs from the manifest used to initialize the session (stale build output), a file was rebuilt/changed between manifest generation and upload so hashes no longer match, or the manifest was trimmed/deduplicated incorrectly so a hash entry is missing.","commonSituations":"Editing files in the asset directory while a deploy is running; a build step regenerating assets after the manifest was computed; caching a manifest from a previous deploy and reusing it; content-addressing collisions or hash computation mismatch between local tooling and Cloudflare.","solutions":["Re-run the deploy without touching the assets directory so the manifest and files are consistent.","Regenerate the asset manifest from the current build output and redeploy.","Verify every entry.Hash in the manifest matches the actual file hash of the file at that path.","Ensure the same manifest object is passed to both uploadAssetManifest and uploadAssets (no mutation in between).","Check for concurrent builds writing to the same output directory during deploy."],"exampleFix":"// before: manifest generated at build time, reused later after files changed\nconst manifest = JSON.parse(fs.readFileSync(\".old-manifest.json\"));\n// after: rebuild manifest from current files immediately before upload\nconst manifest = buildManifestFromDirectory(assetDir);","handlingStrategy":"validation","validationCode":"// before upload: every file hash in manifest must match the on-disk file,\n// and the manifest must be freshly generated from the same build output\nimport { createHash } from \"crypto\";\nfunction validateManifest(manifest, dir) {\n  for (const [file, entry] of Object.entries(manifest)) {\n    const p = path.join(dir, file);\n    if (!fs.existsSync(p)) throw new Error(`manifest file missing: ${p}`);\n    const h = createHash(\"md5\").update(fs.readFileSync(p)).digest(\"hex\");\n    if (h !== entry.hash) throw new Error(`stale manifest: hash mismatch for ${file}`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await uploadAssets(manifest, dir);\n} catch (e) {\n  if (/hash .* not found in manifest/.test(String(e))) {\n    throw new Error(\"Manifest out of sync with build output - regenerate the manifest and redeploy without modifying assets mid-deploy\");\n  }\n  throw e;\n}","preventionTips":["Always generate the manifest immediately before upload from the same build output","Never mutate or trim the manifest between session init and upload","Avoid concurrent builds writing to the same asset directory","Freeze (don't edit) assets during a deployment"],"tags":["assets","manifest","hash-mismatch","build"],"backgroundTag":"asset-hash-not-in-manifest","analyzedSha":"a0bd20f762883e72a35caccb4896c42ce5b3f707","analyzedAt":"2026-08-30T11:26:00.383Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}