{"record":{"id":"41df2e3e4246fe56","repo":"hcengineering/platform","slug":"storage-error-error-error","errorCode":null,"errorMessage":"Storage error ${error.error}","messagePattern":"Storage error (.+?)","errorType":"exception","errorClass":"StorageError","httpStatus":null,"severity":"error","filePath":"foundations/core/packages/api-client/src/storage/client.ts","lineNumber":137,"sourceCode":"    }\n    const result = (await response.json()) as BlobUploadResult[]\n    if (Object.hasOwn(result[0], 'id')) {\n      const fileResult = result[0] as BlobUploadSuccess\n      return {\n        _class: core.class.Blob,\n        _id: fileResult.id as Ref<Blob>,\n        space: core.space.Configuration,\n        modifiedOn: fileResult.metadata.lastModified,\n        modifiedBy: core.account.System,\n        provider: '',\n        contentType: fileResult.metadata.contentType,\n        etag: fileResult.metadata.etag,\n        version: null,\n        size: fileResult.metadata.size\n      }\n    } else {\n      const error = (result[0] as BlobUploadError) ?? 'Unknown error'\n      throw new StorageError(`Storage error ${error.error}`)\n    }\n  }\n\n  async partial (objectName: string, offset: number, length?: number): Promise<Readable> {\n    const url = this.getObjectUrl(objectName)\n\n    const response = await wrappedFetch(url, {\n      headers: {\n        ...this.headers,\n        Range: length !== undefined ? `bytes=${offset}-${offset + length - 1}` : `bytes=${offset}`\n      }\n    })\n\n    if (response.body == null) {\n      throw new StorageError('Missing response body')\n    }\n    return Readable.from(response.body)\n  }","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/core/packages/api-client/src/storage/client.ts#L119-L155","documentation":"After a successful HTTP upload, the service responds with a JSON array of BlobUploadResult entries. If the first entry lacks an 'id' field, it is treated as a BlobUploadError and put() throws StorageError(`Storage error ${error.error}`), surfacing the application-level error string returned by the upload service despite the HTTP 2xx status.","triggerScenarios":"Calling storageClient.put(...) where the upload service returns 200 with an error object in the response body — e.g. quota exceeded, invalid workspace, storage backend failure — or when result[0] is undefined/empty (error.error then throws TypeError on undefined... practically when the array's first element is a { key, error } object).","commonSituations":"Workspace storage quota exhausted; MinIO/S3 backend credentials broken server-side while the HTTP gateway still answers 200; uploading to a workspace id that doesn't exist; service version mismatch where the response shape changed.","solutions":["Read the error string in the StorageError message to get the server-side reason.","Verify the workspace has storage quota and exists.","Check upload service logs and its object-storage (S3/MinIO) credentials.","Confirm client and server package versions agree on the BlobUploadResult response contract."],"exampleFix":"// before\nconst blob = await storage.put(name, stream, contentType) // may throw StorageError: Storage error quota exceeded\n// after\ntry {\n  const blob = await storage.put(name, stream, contentType)\n} catch (err) {\n  if (err instanceof StorageError && /quota/i.test(err.message)) {\n    throw new Error('Workspace storage quota exceeded — free up space or upgrade plan')\n  }\n  throw err\n}","handlingStrategy":"try-catch","validationCode":"// Check quota/state hints client-side if your platform exposes them; otherwise validate inputs\nif (!objectName || !contentType) throw new Error('put requires objectName and contentType')","typeGuard":"function isStorageError(e: unknown): e is StorageError {\n  return e instanceof StorageError\n}","tryCatchPattern":"try {\n  return await storage.put(name, stream, contentType)\n} catch (e) {\n  if (isStorageError(e) && e.message.startsWith('Storage error')) {\n    // application-level rejection despite HTTP 200; message holds the reason\n    console.error('Upload rejected by service:', e.message)\n  }\n  throw e\n}","preventionTips":["Monitor workspace storage quota and alert before exhaustion.","Verify the upload service's object-storage backend credentials in its own health checks.","Keep client and server api/storage package versions aligned on the BlobUploadResult contract.","Log the StorageError message verbatim — it carries the server's reason string."],"tags":["storage","upload","api-response","blob"],"backgroundTag":"upload-rejected","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}