Why Your Bot Works Fine Until It Restarts: A State Synchronization Bug
My Minecraft-Discord bot was flooding the channel with villager death notifications every time the server restarted. During normal gameplay, filtering worked perfectlyâonly real player deaths made it to Discord. But restart the server? Every mob death poured through.
The Initial Investigation
The death notification showed up in my terminal with a distinctive format:
Villager class_1646['Villager'/89054, l='ServerLevel[TubaBoneWorld2025]', x=-137.70, y=96.00, z=-399.70] died, message: 'Villager was squished too much'
I traced the issue to minecraft_integration.py and its regex patterns for parsing log events:
DEATH_PATTERN = re.compile(r'\[(\d{2}:\d{2}:\d{2})\] \[(?:Server thread|Async Chat Thread[^\]]*)/INFO\]: (\w+) (.+)')
The pattern uses (\w+) as the first capture groupââone or more word characters.â This means âVillagerâ matches just as readily as âSteveâ would. The pattern is intentionally permissive because Minecraft has dozens of death message variants, and maintaining an exhaustive list would be fragile. Better to catch everything and filter downstream.
The State Synchronization Problem
The bot maintains an online players list in DynamoDB. The filtering logic seemed simple: if the âusernameâ from the death message isnât in the online players list, ignore it. Villagers arenât players, so theyâd never appear in that list.
But server restarts expose a timing gap:
6:00:00 - Minecraft server process starts
6:00:02 - Server begins writing to log file
6:00:03 - Log watcher detects activity, starts processing
6:00:03 - Villager gets crushed by falling block, death logged
6:00:15 - DynamoDB sync completes, player list populated
During that 12-second window, the player list is empty. The filtering logic checks if âVillagerâ is in an empty list, gets False, and then what? The original code didnât handle this case:
def should_forward_death(username, death_message):
online_players = get_online_players() # Returns [] during startup
if username in online_players:
return True
# No explicit handling for empty player list
# Falls through to return True by default
With an empty player list, the code couldnât distinguish âthis is a mob deathâ from âthis player might be online but we donât know yet.â
The Fix: Intrinsic Checks Over Stateful Lookups
Instead of depending on synchronized state, the fix examines the log line itself:
if 'class_' in raw_line and "died, message:" in raw_line:
return None
Minecraft logs entity deaths with a distinctive format that includes class_XXXX identifiers and coordinate data. Player deaths never include this metadataâtheyâre logged as simple messages like Steve was slain by Zombie. This format check is intrinsic to the data; it doesnât depend on any external state being synchronized.
Takeaways
Test your restart scenarios. Steady-state behavior can hide timing bugs that only appear during initialization.
Prefer intrinsic checks over stateful lookups. If you can identify data by its format rather than comparing against external state, you eliminate timing dependencies entirely.
Show AI assistants the actual error. The raw log line contained all the cluesâthe class_1646 identifier and coordinate tuple immediately revealed the distinguishing characteristic.
The villager filter now works regardless of timing. The class_ identifier is baked into Minecraftâs logging format. It doesnât care whether DynamoDB has finished syncing, and neither does my bot.