vibescrobble post now writes the scrobble to a crash-safe spool under the CLI's state directory (one file per scrobble, fsynced and linked into place), prints the at:// URI it will have under a record key chosen at post time, and returns. A detached drainer publishes each account's spool in order, one drainer per account under an flock, reading the session from disk before every scrobble. It paces a backlog to 5 a minute, below the index's per-account cap. A server that is not there is retried with jittered backoff up to a minute, for VIBESCROBBLE_SPOOL_GIVE_UP seconds (0 to 3600, default 600). Every write that does not succeed (a 5xx, a 429, a 4xx, a timeout) is read back with getRecord first, so a key already holding this scrobble counts as published even on a PDS that answers 500 for a taken key. A scrobble refused for good moves to failed/. VIBESCROBBLE_SPOOL_MAX_AGE (60 s to 30 d, default a day) moves older scrobbles to expired/, and VIBESCROBBLE_SPOOL_MAX_ITEMS (1 to 10000, default 1000) drops the oldest past it. Each case leaves a notice that the next post or status prints once.
Nothing is published for an account nobody is signed in as. logout drops and lists what was waiting and deletes the spool, and a drainer mid-drain stops at its next scrobble. Scrobbles posted while a sign-in was under way are dropped, with one notice, if that sign-in is declined or abandoned. Every login, post, status, flush, repo and logout run recovers every spool on the machine, not only the account it names, so a finished context's spool is still published or dropped. A spooled entry can carry the end of its usage window and a sealed usage, and the drainer seals an unsealed entry before publishing. The seal does no counting yet.
status shows what is waiting. flush publishes and waits, and post --wait does the same for one scrobble. post latency goes from 0.20 s to 0.03 s with the PDS up, and from 29 s (scrobble lost) to 0.02 s (scrobble kept) with it down. A plugin test runs the real binary under the hook against a server that never answers.
🤖 Generated with Claude Code