hcengineering/platform · error · StorageError

Storage error ${error.error}

Error message

Storage error ${error.error}

What it means

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.

Source

Thrown at foundations/core/packages/api-client/src/storage/client.ts:137

    }
    const result = (await response.json()) as BlobUploadResult[]
    if (Object.hasOwn(result[0], 'id')) {
      const fileResult = result[0] as BlobUploadSuccess
      return {
        _class: core.class.Blob,
        _id: fileResult.id as Ref<Blob>,
        space: core.space.Configuration,
        modifiedOn: fileResult.metadata.lastModified,
        modifiedBy: core.account.System,
        provider: '',
        contentType: fileResult.metadata.contentType,
        etag: fileResult.metadata.etag,
        version: null,
        size: fileResult.metadata.size
      }
    } else {
      const error = (result[0] as BlobUploadError) ?? 'Unknown error'
      throw new StorageError(`Storage error ${error.error}`)
    }
  }

  async partial (objectName: string, offset: number, length?: number): Promise<Readable> {
    const url = this.getObjectUrl(objectName)

    const response = await wrappedFetch(url, {
      headers: {
        ...this.headers,
        Range: length !== undefined ? `bytes=${offset}-${offset + length - 1}` : `bytes=${offset}`
      }
    })

    if (response.body == null) {
      throw new StorageError('Missing response body')
    }
    return Readable.from(response.body)
  }

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Read the error string in the StorageError message to get the server-side reason.
  2. Verify the workspace has storage quota and exists.
  3. Check upload service logs and its object-storage (S3/MinIO) credentials.
  4. Confirm client and server package versions agree on the BlobUploadResult response contract.

Example fix

// before
const blob = await storage.put(name, stream, contentType) // may throw StorageError: Storage error quota exceeded
// after
try {
  const blob = await storage.put(name, stream, contentType)
} catch (err) {
  if (err instanceof StorageError && /quota/i.test(err.message)) {
    throw new Error('Workspace storage quota exceeded — free up space or upgrade plan')
  }
  throw err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Check quota/state hints client-side if your platform exposes them; otherwise validate inputs
if (!objectName || !contentType) throw new Error('put requires objectName and contentType')

Type guard

function isStorageError(e: unknown): e is StorageError {
  return e instanceof StorageError
}

Try / catch

try {
  return await storage.put(name, stream, contentType)
} catch (e) {
  if (isStorageError(e) && e.message.startsWith('Storage error')) {
    // application-level rejection despite HTTP 200; message holds the reason
    console.error('Upload rejected by service:', e.message)
  }
  throw e
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/41df2e3e4246fe56. Report an issue: GitHub.