toeverything/AFFiNE · error · BlobInvalid

blob_invalid

blob_invalid

Error message

Missing upload content length

What it means

BlobInvalid('Missing upload content length') at blob.ts:376, inside `WorkspaceBlobStorage.createProxyUploadUrl`. When the proxy (signed-URL) mode is enabled, building a proxy upload URL requires `metadata.contentLength`; if undefined, the URL cannot be token-signed (contentLength is a canonical token field) so the call is rejected before minting.

Source

Thrown at packages/backend/server/src/core/storage/wrappers/blob.ts:376

    if (!usePresignedURL?.enabled) {
      return;
    }
    return {
      signKey: usePresignedURL.signKey || undefined,
      urlPrefix: usePresignedURL.urlPrefix || undefined,
    };
  }

  private createProxyUploadUrl(
    workspaceId: string,
    key: string,
    metadata: PutObjectMetadata | undefined,
    proxy: UploadProxyConfig
  ) {
    const contentType = metadata?.contentType ?? 'application/octet-stream';
    const contentLength = metadata?.contentLength;
    if (contentLength === undefined) {
      throw new BlobInvalid('Missing upload content length');
    }
    const expiresAt = new Date(Date.now() + SIGNED_URL_EXPIRED * 1000);
    const expiresAtSeconds = Math.floor(expiresAt.getTime() / 1000);
    const token = createStorageUploadToken(
      PROXY_UPLOAD_PATH,
      [workspaceId, key, contentType, contentLength],
      expiresAtSeconds,
      proxy.signKey
    );
    return {
      url: this.linkProxyUrl(proxy.urlPrefix, PROXY_UPLOAD_PATH, {
        workspaceId,
        key,
        contentType,
        contentLength,
        expiresAt: expiresAtSeconds,
        token,
      }),

View on GitHub (pinned to 26c515e050)

Solutions

  1. Always pass `metadata.contentLength` (the exact byte size of the blob) when calling `presignPut` under proxy mode.
  2. If the size is unknown, switch to non-proxy presign (unset `usePresignedURL.enabled`/`signKey`) or buffer the blob first to measure it.
  3. Type the call site so contentLength is required when proxy mode is on.

Example fix

// before
const url = await blob.presignPut(ws, key, { contentType }); // proxy mode -> 273

// after
const url = await blob.presignPut(ws, key, { contentType, contentLength: buf.byteLength });
Defensive patterns

Strategy: validation

Validate before calling

// Always supply contentLength when proxy mode is enabled.
function assertProxyMetadata(metadata: { contentType?: string; contentLength?: number } | undefined) {
  if (metadata?.contentLength === undefined) {
    throw new Error('metadata.contentLength is required in proxy upload mode');
  }
}
assertProxyMetadata(metadata);
const url = await blob.presignPut(ws, key, { contentType, contentLength: buf.byteLength });

Type guard

function hasContentLength(m: unknown): m is { contentType?: string; contentLength: number } {
  return typeof m === 'object' && m !== null && typeof (m as any).contentLength === 'number';
}

Prevention

When it happens

Trigger: Calling `presignPut(workspaceId, key, metadata)` while `usePresignedURL.enabled` + `signKey` are set in storage config, and `metadata` is undefined or has no `contentLength`. The proxy branch is taken (`config.signKey` truthy) and `metadata?.contentLength` is undefined.

Common situations: Internal caller passes `{ contentType }` only, assuming the server can infer size; caller passes `metadata: undefined`; refactor dropped the contentLength field; proxy mode newly enabled without updating callers.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/4de3436be74dbd3f. Report an issue: GitHub.