One user message carrying several links opens one prompt per post; every
prompt used to reply to that shared link message, so they were
indistinguishable. The prompt now replies to the first media message it
edits, falling back to the link message when there is none.
zip 8.6.0 -> 9.1.2 with default-features = false, features = ["deflate"]: ugoira frame zips are stored/deflate only, so the default codec set was dead weight — and its zstd 0.13 stack (RUSTSEC-2026-0391, -0395..-0406, -0410) has no fixed release inside zip <=9.0.0's ^0.13.3 pin. ctutils 0.4.2 (RUSTSEC-2026-0385) leaves with zip's aes-crypto feature.
tokio 1.53.3 (RUSTSEC-2026-0386), rustls-webpki 0.103.16 (RUSTSEC-2026-0382), webpki-roots 1.0.10 (RUSTSEC-2026-0381).
cargo audit on the new lock reports only the unmaintained dotenv (RUSTSEC-2021-0141) and proc-macro-error2 (RUSTSEC-2026-0173) warnings, which denyWarnings: false does not fail on. A new pixiv test pins that both stored and deflate ugoira frames decode with the reduced zip feature set.
A handler whose row was re-leased while it sat between heartbeats (a
>=120 s stall) still ran the channel forward, opened the edit prompt and
released the keep-alive temp media the new holder was using; only the
terminal write was token-guarded, which left the side effects to run
twice. The lease is now exposed to the handler on a task-local
(Lease/lease_still_held) and the completion path re-reads it, since the
loss may have gone unnoticed by the heartbeat. The forward retry is gated
the same way.
A Permanent forward failure kept the prompt live, so the next Confirm
rebuilt the task with forward_offset 0 and copied the batches that had
already reached the channel a second time. A failure whose task offset is
non-zero now settles the prompt (as the retryable path does); a failure
that copied nothing still leaves it live for a retry.
The pipeline read settings through get(), which degrades a failed read to
defaults. A send then went out with the chat's channel forward and
edit-before-forward silently ignored, and the task it built carried those
defaults into the retry payload. try_get() is the strict read; an
unreadable row now drops the send and tells the user.
A heartbeat write error abandoned the attempt at once. The lease is still
valid until locked_until, so the attempt was dropped for nothing: the row
was recovered later and the task re-ran from the start, although its side
effects (a sent album, a copied post) may already have happened. A failed
beat now retries at the next tick; the attempt is abandoned only once the
next beat could land at or past the last confirmed expiry.