What happens
The queue docs say completable messages are removed from storage when they're received and are not redelivered if processing fails. The runtime does the opposite. A completable message stays stored until complete() and is delivered again after the actor restarts. Anyone who trusts the docs writes handlers that aren't safe to run twice. #5349 is a user asking for ACK/NACK because of this exact sentence.
Where
docs/content/docs/queues.mdx, "Completable messages":
- `message.complete()` resolves a sender waiting on `wait: true` (or `enqueueAndWait`). It does not change durability: messages are removed from queue storage when they are received, not when they are completed.
- If processing fails before `message.complete()`, the message is not redelivered, and any waiting sender times out instead of receiving a completion.
Why it happens
In rivetkit-rust/packages/rivetkit-core/src/actor/queue.rs, try_receive_batch returns early for completable receives without calling remove_messages. The row is only deleted by complete() or reset(), and nothing purges un-completed messages on startup, so the next next() after a restart returns it again. The TypeScript queue-manager.ts from before the Rust port did the same (if (!completable) await this.#removeMessages(selected)), so this looks like a docs mistake rather than a runtime regression.
Reproduction
A test in rivetkit-core/tests/queue.rs that receives a message with completable: true, drops the actor context without completing it, rebuilds the context on the same SQLite handle and calls next() gets the same message id back. The existing stale_completion_does_not_decrement_queue_size_twice test also shows the message is still stored after a completable receive.
Expected behaviour
The docs match the runtime: completable messages are removed on complete(), and un-completed ones are delivered again after a restart.
Suggested fix
Update the two bullets to describe the current behavior and tell users to make completable handlers safe to run more than once. Happy to open a PR with the docs change and a test that pins the behavior.
Environment
- upstream/main at
ce2d0dd5f
- Linux 7.2.5-3-omarchy
- rustc 1.98.1 (48a229cea 2026-09-01)
What happens
The queue docs say completable messages are removed from storage when they're received and are not redelivered if processing fails. The runtime does the opposite. A completable message stays stored until
complete()and is delivered again after the actor restarts. Anyone who trusts the docs writes handlers that aren't safe to run twice. #5349 is a user asking for ACK/NACK because of this exact sentence.Where
docs/content/docs/queues.mdx, "Completable messages":Why it happens
In
rivetkit-rust/packages/rivetkit-core/src/actor/queue.rs,try_receive_batchreturns early for completable receives without callingremove_messages. The row is only deleted bycomplete()orreset(), and nothing purges un-completed messages on startup, so the nextnext()after a restart returns it again. The TypeScriptqueue-manager.tsfrom before the Rust port did the same (if (!completable) await this.#removeMessages(selected)), so this looks like a docs mistake rather than a runtime regression.Reproduction
A test in
rivetkit-core/tests/queue.rsthat receives a message withcompletable: true, drops the actor context without completing it, rebuilds the context on the same SQLite handle and callsnext()gets the same message id back. The existingstale_completion_does_not_decrement_queue_size_twicetest also shows the message is still stored after a completable receive.Expected behaviour
The docs match the runtime: completable messages are removed on
complete(), and un-completed ones are delivered again after a restart.Suggested fix
Update the two bullets to describe the current behavior and tell users to make completable handlers safe to run more than once. Happy to open a PR with the docs change and a test that pins the behavior.
Environment
ce2d0dd5f