Runi never drops a record because the network was down, and never silently swallows one the workspace refused. Both go to the outbox, and they land in two different places with two different meanings.
Pending versus dead#
| Pending | Dead letter | |
|---|---|---|
| Means | Not delivered yet. The network was unavailable, or the attempt failed transiently. | Delivered and refused. The workspace said no. |
| Resolves | By itself, with bounded backoff. | Never, without a decision from you. |
| Doctor badge | warn | FAIL |
| You should | Nothing. | Retry or discard, once you know why they were refused. |
Pending writes#
warn outbox 14 write(s) waiting (oldest 2 h ago) — retries use
bounded backoff and do not block unrelated records
Retries are bounded and per record, so one stubborn write cannot hold up the queue behind it. Working on a plane is the normal case for this line, and it clears itself when you reconnect.
“belong to a previous login and will never send”#
Writes queued under credentials that are no longer current. They cannot be delivered as anyone, so the doctor counts them separately instead of letting them inflate the pending number forever:
warn outbox 31 write(s) waiting (oldest 6 d ago) 27 of them belong
to a previous login and will never send
Discard those. They record work done under an account this machine is no longer signed in as.
Dead letters#
FAIL dead-letter 863 rejected write(s) need review — retry with
--retry-dead <id> or discard with --discard-dead <id>
Why writes get refused#
| Cause | Tell-tale | Then |
|---|---|---|
| The account was read-only | A large count, and a publish line reading access-denied. |
Get member access first. Retrying before that just refuses them again. |
| The account was removed | The same shape. From the daemon's side the two are indistinguishable. | Discard, unless you are being re-added. |
| The repository stopped being tracked | Refusals starting at the moment somebody removed it. | Discard, or re-add the repository first if the removal was a mistake. |
| A schema the workspace has not got | A small, specific set. Receipts are the usual one. | Discard. The daemon already backs off on that path instead of dead-lettering it repeatedly. |
Careful
Fix the cause before you retry. Retrying eight hundred writes into a workspace that will refuse them again produces eight hundred fresh dead letters and no progress.
The commands#
# one write, named by the id the doctor printed
$ runi doctor --retry-dead 1757012345678-a1b2c3
$ runi doctor --discard-dead 1757012345678-a1b2c3
# every dead letter
$ runi doctor --retry-dead all
# only those refused within a window
$ runi doctor --discard-dead all --before 2026-09-01
$ runi doctor --retry-dead all --since 2026-09-05
$ runi doctor --retry-dead all --since 2026-09-01 --before 2026-09-04
You cannot retry and discard in the same command, and an id or
all is required. There is no accidental bulk action.
Retried writes move back to the pending outbox and go on the next publish. The windows take an ISO date and are read from the timestamp in the file name, so narrowing costs nothing.
A sensible recovery, after a spell as a viewer#
-
Get the access first#
Ask an owner or admin for member access, then confirm with
runi doctorthatpublishis no longeraccess-denied. -
Decide what is still worth having#
Sessions from last week are usually noise. The last day or two may be worth replaying. Old records arriving as if they were new is its own small confusion for teammates.
-
Retry the recent ones#
terminal$ runi doctor --retry-dead all --since 2026-09-05 -
Discard the rest#
terminal$ runi doctor --discard-dead all --before 2026-09-05 -
Confirm#
terminal$ runi doctor … ok outbox empty — nothing waiting to replay
Is any of this urgent?#
No. A full outbox does not affect your agent. Hooks exit successfully on every failure, because a broken network must never break a session. The cost of ignoring it is that your teammates are missing context they would otherwise have had.