cloudflare / cloudflare/workerd
Queues: producer-side limit failures throw untyped Error with no code — add typed errors + a size helper
- Dominant language
- C++
- Stars
- 8.7k
- Forks
- 739
- Avg merge
- 2d 20h
- Merged PRs (30d)
- 174
Description
## 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"](https://www.answeroverflow.com/m/1247558034213834804)).
3. **Document all producer errors** (message size, batch size, batch count, throughput/429) on the [Limits](https://developers.cloudflare.com/queues/platform/limits/) and [JavaScript APIs](https://developers.cloudflare.com/queues/configuration/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.
Contributor guide
Research direction
Start by tracing the Queue.send() and Queue.sendBatch() entry points and the local broker checks for message size, batch size, and batch count. Compare those runtime limits with the Limits and JavaScript APIs documentation pages. Done means producer-limit failures expose stable typed or coded errors and structured values, a first-party size calculation is available, and retryability is documented.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, javascript
- Domain
- backend-api-design, documentation
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100