← All postsAugust 14, 2026

A silent data-loss bug, and the one-line fix that ended it

automationbuild-loglucy-tutor

For a few weeks, Lucy Tutor — the IELTS writing tutor I built for myself — had a bug that only showed up sometimes: a completed drill day would just be missing the next time I loaded the app. No error, no warning. The data was there when I graded the essay, gone by the time I refreshed.

The state lives in a JSON file on GitHub, and every write goes through the GitHub Contents API, which requires the current file SHA to accept an update. I was firing several writes in parallel with Promise.all — drill history, error log, collocation bank — because parallel felt faster and there was no obvious reason not to.

That was the bug. Two writes racing against the same file both read the same starting SHA, both tried to commit, and the second one either failed outright or silently overwrote the first with stale data. Nothing crashed. It just lost whichever write lost the race.

The fix was one word: await each write in sequence instead of Promise.all-ing them. Slower, technically — a few hundred milliseconds instead of running concurrently — and completely correct, because now every write reads the SHA the previous write actually produced.

The lesson generalizes past this one bug: “parallel is faster” is only true when the operations don’t share mutable state. Once they do, parallel isn’t an optimization, it’s a race condition wearing a performance improvement as a disguise. Same instinct that applies to running ad platform pulls against a shared budget ledger — sequence matters more than speed the moment two writes can touch the same record.