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
- Always pass `metadata.contentLength` (the exact byte size of the blob) when calling `presignPut` under proxy mode.
- If the size is unknown, switch to non-proxy presign (unset `usePresignedURL.enabled`/`signKey`) or buffer the blob first to measure it.
- 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
- Buffer the blob to measure byteLength before presigning under proxy mode.
- Make contentLength required at the type level when `usePresignedURL.enabled && signKey`.
- Audit presignPut call sites after enabling proxy mode.
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
- Failed to read image size
- ErrorCode.DefaultRuntimeError
- Invalid key for: ${key}
- SchemaValidateError
- bad_request
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/4de3436be74dbd3f.
Report an issue: GitHub.