Nightly Build / build (push) Successful in 7m40s
Pairing failed with "invalid or expired pairing code" on a code the bot had just sent. handle_pairing read the pending codes from the in-memory `shared.bindings` cache, which is refreshed from the ConfigKeyUpdated broadcast — a lossy 64-slot bus. One dropped event is enough for that cache to keep a pending entry the store no longer has; the "reuse an existing code for this chat" branch then hits, and that branch does not write. The user gets a code, and the web page — which resolves it against the store — cannot find it. Before the move to the config store, this path re-read the file on every message and could not drift. The cache stays where it earns its keep, the chat_id → user_id lookup on every inbound message, where a stale read costs one message. Issuing a code now reads the store. Two silent failures on the same path, each able to produce the same symptom while hiding its cause: handle_pairing sent the code even when the write had failed — it logged and carried on — so the error surfaced later, somewhere else, as a code that simply would not bind. It now says so in the chat and hands out nothing. load_config turned an unparseable blob into `unwrap_or_default()`: no bindings, no pending codes. Every writer here saves the whole blob back, so the next pairing message would have overwritten the real config with that default and taken every binding on the box with it. An absent key is still an empty config — that is a fresh install — but an unreadable one is now an error that callers propagate, including start(), which fails loudly rather than running on a cache it knows is wrong.