Skip to content

Queues: producer-side limit failures throw untyped Error with no code — add typed errors + a size helper #6834

Description

@alexander-zuev

TL;DR

Queue messages were being dropped because they were over platform limits -> wanted to handle typed / tagged errors from queue binding but it requires parsing/relying on implicit logic coded in error message strings. Request: please throw or return typed errors + document them so that we can safely rely on them in prod

What we'd like

  1. Typed, coded errors for producer-limit failures, with stable code values and a documented catalog, e.g.:

    • QUEUE_MESSAGE_TOO_LARGE
    • QUEUE_BATCH_TOO_LARGE
    • QUEUE_BATCH_TOO_MANY_MESSAGES
    • QUEUE_THROUGHPUT_EXCEEDED (429 / "Too Many Requests")

    Distinguishable via instanceof and/or a stable error.code, with structured fields (limit, actual) rather than only interpolating them into a human string.

  2. A first-party size helper so producers don't reverse-engineer how the platform computes serialized size (content-type-dependent v8/json encoding + ~100 bytes internal metadata), e.g. queue.calculateMessageSize(body, options?), or a documented exact formula. This is already a recurring community question (e.g. "how to calculate queue message size the way CF does").

  3. Document all producer errors (message size, batch size, batch count, throughput/429) on the Limits and JavaScript APIs pages, including whether each is retryable.

Why it matters

A common, correct producer pattern is the transactional outbox: persist events, then relay them to a queue. If one event can never be sent (e.g. it exceeds 128 KB), the producer must classify the failure as permanent vs transient to decide between dead-lettering and retrying.

Today that decision can only be made by substring-matching English error text. Get it wrong — treat a permanent failure as transient — and the unsendable message becomes a head-of-line poison pill that blocks delivery for everything behind it and drives an infinite retry loop. Typed errors with stable codes make the permanent/transient split reliable; a size helper lets producers guard pre-flight and avoid the failed round-trip entirely.

Current behavior

Queue.send() / Queue.sendBatch() reject with a plain Error when a producer-side limit is exceeded — error.name === "Error", error.code === undefined, no own enumerable properties. The only programmatic signal is the message string, and the wording is inconsistent:

Trigger Message
message > 128 KB (send) Queue send failed: message length of 200029 bytes exceeds limit of 128000
message > 128 KB inside a batch message in batch has length 331077 bytes which exceeds single message size limit of 128000 bytes
batch total bytes over cap Queue sendBatch failed: batch size of 360116 bytes exceeds limit of 256000
batch count > 100 Queue sendBatch failed: batch message count of 101 exceeds limit of 100

Note the same 128 KB limit is worded two different ways depending on send vs sendBatch — exactly why string-matching is unsafe.

There is also a docs-vs-runtime discrepancy: docs state a 256 KB batch limit, but the local broker enforces the check at 288 KB (256 KB + ~32 KB) while the error text still reports 256000. Clarifying the authoritative number in both the docs and the error text would help.

Environment

  • workerd / Wrangler / Miniflare (local broker constants: per-message 128000, batch count 100, batch bytes 288000).
  • Production Queues exhibits the same untyped-error behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions