{"record":{"id":"166839abd16a56d8","repo":"toeverything/AFFiNE","slug":"blob-invalid-166839","errorCode":"blob_invalid","errorMessage":"Blob size mismatch","messagePattern":"Blob size mismatch","errorType":"exception","errorClass":"BlobInvalid","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/blob.ts","lineNumber":258,"sourceCode":"      mime: mime || 'application/octet-stream',\n      kind: 'blob',\n    });\n    this.assertStorageReservation(reservation);\n    if (reservation.alreadyUploaded) {\n      return {\n        method: BlobUploadMethod.GRAPHQL,\n        blobKey: key,\n        alreadyUploaded: true,\n      };\n    }\n    if (!reservation.reservationId)\n      throw new BlobInvalid('Missing blob reservation');\n\n    let record = await this.models.blob.get(workspaceId, key);\n    mime = mime || 'application/octet-stream';\n    if (record) {\n      if (record.size !== size) {\n        throw new BlobInvalid('Blob size mismatch');\n      }\n\n      if (record.status === 'completed') {\n        const existingMetadata = await this.storage.head(workspaceId, key);\n        if (!existingMetadata) {\n          // record exists but object is missing, treat as a new upload\n          record = null;\n        } else if (existingMetadata.contentLength !== size) {\n          throw new BlobInvalid('Blob size mismatch');\n        } else {\n          return {\n            method: BlobUploadMethod.GRAPHQL,\n            blobKey: key,\n            alreadyUploaded: true,\n          };\n        }\n      } else {\n        mime = record.mime;","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/core/workspaces/resolvers/blob.ts#L240-L276","documentation":"Thrown by BlobResolver.createBlobUpload when a blob record already exists for (workspaceId, key) but the declared size differs from record.size — the session's declared size is immutable, so re-initializing the same key with a different size is rejected as BlobInvalid('Blob size mismatch').","triggerScenarios":"Calling createBlobUpload twice for the same key with different size arguments: retries after the file changed underneath, two clients racing on one key, or a re-stat of a still-being-written file producing a new size.","commonSituations":"Retry logic that re-inits with freshly stat'ed metadata; concurrent uploads of same-named files; tests reusing fixed keys with variable-size fixtures.","solutions":["Reuse the exact size (and key) from the original createBlobUpload call when retrying","If content/size genuinely changed, upload under a new unique blob key","Delete the stale blob record/completed blob before re-initializing with different metadata","Derive key+size once per file client-side and reuse them across the whole retry sequence"],"exampleFix":"// before\nconst init = await createBlobUpload(wsId, key, file.size); // file still growing → mismatch on retry\n// after\nconst size = await settledFileSize(file); // stat once after write completes\nconst init = await createBlobUpload(wsId, key, size); // always same (key, size) pair","handlingStrategy":"validation","validationCode":"// freeze (key, size) once per file and reuse across retries\nconst uploadDescriptor = {\n  key: await deriveBlobKey(file),\n  size: await finalSize(file), // stat only after the file is fully written\n};\n// every retry path uses uploadDescriptor.size — never re-stat mid-write\nconst init = await createBlobUpload(wsId, uploadDescriptor.key, uploadDescriptor.size, mime);","typeGuard":"function isBlobSizeMismatch(e: unknown): e is { code: 'blob_invalid'; message: string } {\n  return typeof e === 'object' && e !== null && (e as any).code === 'blob_invalid' && /size mismatch/i.test((e as any).message ?? '');\n}","tryCatchPattern":"try {\n  return await createBlobUpload(wsId, key, size, mime);\n} catch (e) {\n  if (isBlobSizeMismatch(e)) {\n    const freshKey = newBlobKey(); // content changed — restart under a new key\n    return createBlobUpload(wsId, freshKey, size, mime);\n  }\n  throw e;\n}","preventionTips":["Compute key and size once per immutable file version and reuse them on every retry","Only stat files after writes complete (no uploading files still being written)","Don't share one blob key between concurrent uploads of different content","In tests, derive keys from fixtures so sizes stay deterministic"],"tags":["upload","validation","blob","idempotency"],"backgroundTag":"upload-size-mismatch","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}