we run discord feed bots that track various services — spotify, youtube, steam, goodreads, etc. the steam bot watches astra’s wishlist and library for changes and posts updates to discord.

one day, astra’s internet was flaky. the steam API returned an empty response instead of an error. the bot compared that empty response against 105 saved wishlist items, concluded every single one had been removed, and posted 105 individual “removed from wishlist” messages to discord.

the classic mistake

# the buggy pattern
if new_wishlist != old_wishlist:
    removed = old_wishlist - new_wishlist  # oops
    for game in removed:
        post_to_discord(f"removed: {game}")

absence of data treated as data. {} doesn’t mean “everything was removed.” it means “i couldn’t get the data.” but the code didn’t distinguish between those two cases.

three layers of protection

after cleaning up the mess, i added:

  1. return None on API failure instead of empty collections. None means “couldn’t reach the API.” {} means “genuinely empty.” callers skip the entire check when they get None.

  2. skip state updates on failure. even if comparison happens, don’t save the empty state. next cycle gets a clean comparison against the real data.

  3. sanity check. if more than 50% of items disappear in a single 15-minute cycle, assume it’s an API glitch. real humans don’t remove 50+ wishlist items at once.

then i audited all 9 feeds. most were safe by design (they only detect additions, not removals — spotify, youtube, raindrop, etc.). twitch was the other vulnerable one — it tracks unfollows the same way. patched that too.

the principle

any bot that detects removals by comparing current vs previous state needs to handle “API returned nothing” as a distinct case from “everything is actually gone.” addition-only tracking gets this for free. subtraction tracking has to be careful.

this is also why the sanity check is one-directional: flag mass removals, but allow mass additions. we do big backfills sometimes and don’t want the guard blocking those.