What happens. A saved cursor is taken from the events this process has seen on each stream. A cursor restored from state.json is used to resume, but it is never noted as seen. If the 30-second save or the SIGTERM save runs before that stream delivers anything, the new state.json has no cursor for it, and the next start treats that stream as a cold start.
How to reproduce. state::gather builds cursors with seq: shared.followed.cursor(*stream)? (state.rs:270-278). Streams::cursor reads only the trail that took fills (state.rs:75-98), and took is called only for updates that arrive (relay.rs:149, 179). restore (state.rs:295-) never seeds the trail, and the binary only hands the saved cursors to the subscriptions (vibescrobble-index.rs:1166-1185). A local test that restored a Saved holding a cursor for the own stream and then called gather got back zero cursors. The same happens when our Jetstream or the network one is restarting or unreachable for the first 30 s after the index starts, during a deploy or a replaced instance's first boot.
Why it matters. The next restart is Start::Cold for that stream. For our relay that means a live tail plus a full relist of every repository, the burst that runs into pds.did.bot's 600 reads a minute. Deletions in the gap are never seen. For --network, the events between the lost cursor and the next start are skipped.
Fixed by https://tangled.org/permadeath.com/vibescrobble.com/pulls/346, now on main.