{"record":{"id":"e3e8ce33c3f30533","repo":"schollz/croc","slug":"storage-service-created-accepteddownloads-downl","errorCode":null,"errorMessage":"Storage service created ${acceptedDownloads} downloads instead of ${downloads}","messagePattern":"Storage service created (.+?) downloads instead of (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web/src/protocol/stored.ts","lineNumber":454,"sourceCode":"    uploadExpiresAt: string;\n    chunkSize: number;\n  };\n  if (created.chunkSize !== storedChunkSize) {\n    throw new Error(\n      `Storage service returned unsupported chunk size ${created.chunkSize}`,\n    );\n  }\n  if (!isCapability(created.uploadToken)) {\n    throw new Error(\"Storage service returned an invalid upload capability\");\n  }\n  const downloadsHeader = response.headers.get(\"X-Croc-Downloads\");\n  const acceptedDownloads =\n    downloadsHeader === null ? 1 : Number(downloadsHeader);\n  if (!Number.isSafeInteger(acceptedDownloads) || acceptedDownloads < 1) {\n    throw new Error(\"Storage service returned an invalid download count\");\n  }\n  if (acceptedDownloads !== downloads) {\n    throw new Error(\n      `Storage service created ${acceptedDownloads} downloads instead of ${downloads}`,\n    );\n  }\n  return {\n    share: validateShare({\n      origin: window.location.origin,\n      id: created.id,\n      key,\n    }),\n    uploadToken: created.uploadToken,\n  };\n}\n\nasync function uploadStoredManifest(\n  plan: StoredUploadPlan,\n  created: CreatedStoredUpload,\n  settings: StoredSettings,\n  callbacks: StoredUploadCallbacks,","sourceCodeStart":436,"sourceCodeEnd":472,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/stored.ts#L436-L472","documentation":"Even when X-Croc-Downloads parses, the client requires the granted count to equal the requested downloads value. If the server silently clamps (e.g., requested 5, granted 1) or the header defaults to 1 while the client asked for more, creation aborts: the sender would otherwise hand out a share whose actual download budget differs from what was promised.","triggerScenarios":"uploadStoredFiles({downloads: 5}) against a server that caps or ignores the field and echoes a different X-Croc-Downloads; also when the header is absent (defaults to 1) but downloads !== 1 was requested.","commonSituations":"Server-side maxDownloads lower than the client UI allowed (settings skew); older servers that never implemented multi-download and always grant 1; operators changing limits without refreshing client settings.","solutions":["Request a downloads count within the server's limit — align settings.maxDownloads on the client with the server's actual cap","If the header is optional on your server version, only request the default (omit downloads) so both sides agree on 1","Update the storage service so it either honors the requested count or rejects the request explicitly with 4xx"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const safeDownloads = Math.min(requestedDownloads, settings.maxDownloads);\nif (!Number.isSafeInteger(safeDownloads) || safeDownloads < 1) { throw new Error(\"Invalid downloads setting\"); }","typeGuard":null,"tryCatchPattern":"try { return await uploadStoredFiles({ ..., downloads }); } catch (e) { if (e instanceof Error && e.message.includes(\"instead of\")) { return uploadStoredFiles({ ..., downloads: 1 }); } throw e; }","preventionTips":["Clamp requested downloads to the server-advertised maxDownloads in the UI","Keep client settings.maxDownloads synchronized with the server's actual cap"],"tags":["server-contract","header","limits","stored-transfer"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}