I Deleted All My Own Tasks — The Trap in "The Server Is the Truth" — Build Log #9
Business

I Deleted All My Own Tasks — The Trap in "The Server Is the Truth" — Build Log #9

I recreated my account, logged back in, and everything was gone. The culprit was a safety check I wrote myself. It took minutes to realize there was nothing to restore from, and a few more to learn the free tier had no backups.

KIYODO00
#solo dev#data sync#incident report#backups#build log

I deleted every task I had in my own task app.

I was poking at login behavior — creating test accounts, deleting them. Along the way I deleted my own everyday account too, recreated it with the same Google login, and signed back in.

The screen was empty. No tasks, no lists, no notes.

The only victim was me (no end-user data was affected), but the further I dug into the cause, the colder it got. So here's the write-up.

The culprit was a safety check I wrote myself

The cause was easy to find. At the top of the sync routine I'd added an account-switch safety check:

If the owner ID stored on the device doesn't match the ID currently signed in, wipe the device's local data.

The intent is right. Hand your phone to someone else and you don't want the previous person's tasks sitting there. So wipe on switch. It looks like obvious correctness.

That design was only valid because of an assumption: "wiping is safe, because we can re-fetch from the server." The device is a copy; the server holds the original.

That assumption broke exactly once — this time.

Why it broke

When you delete an account and recreate it, the same email address becomes a different identity internally (the ID assigned behind the scenes changes). Mine was a different ID before and after.

So:

  1. Sign in with the new ID
  2. The owner ID stored on the device is the old one, so they don't match
  3. The safety check fires and wipes local data
  4. Try to re-fetch from the server — but the new ID has nothing there (it was just created)
  5. Device empty, server empty

And because I'd been switching accounts repeatedly while testing, this ran every single time.

"The server is the source of truth" doesn't hold when the server is empty. My code never checked whether the assumption held. It destroyed first.

Then I found there were no backups

"Fine, I'll restore from the daily server backup" — except the free tier of the database I was using had no automatic backups. No point-in-time restore either.

The rows tied to the deleted account were gone as well. There was no path back through the server at all.

That's the moment I understood that nobody was protecting my service's data.

How I recovered

Almost everything came back, via two routes.

1. A manual dump I'd taken on a whim Weeks earlier I'd exported the data by hand for no particular reason, and the file was still sitting on my PC: 152 tasks, 5 lists, 39 notes. I loaded it back under the new ID.

2. What survived in Google Calendar The app writes time-bound tasks out to Google Calendar. Which means the calendar had quietly become a second copy. I pulled the unfinished time-bound tasks created after the dump date back from there.

Final state: 163 tasks, 5 lists, 39 notes. Settings came back through device sync.

The only permanent loss was tasks without a time, created after the dump date — the calendar only ever received the time-bound ones, so that's where the hole is.

What I changed permanently

1. Automatic backups A script that dumps every table daily and keeps 30 days, wired into a scheduled job on my PC. "A manual dump happened to still exist" is not a recovery strategy.

2. Replaced "wipe" with "stash" I rewrote the safety check. Before clearing anything, stash the current data keyed by its owner ID. Then:

  • If no owner is recorded, don't wipe — adopt the data as-is
  • If it really is a different person, stash first, then initialize
  • If the original owner returns, restore their stash

Now, firing by mistake costs nothing. There was never any requirement for that safety check to be destructive.

Takeaways

  • "The real copy is over there" only holds once you've confirmed over-there isn't empty. Never destroy before checking the assumption.
  • A recreated account is a different person. Same email doesn't mean same identity.
  • Before writing a delete, ask whether it can be a stash instead. Recoverable incidents aren't incidents.
  • Don't assume your plan includes backups. Go read what you're actually paying for.
  • An unintended second copy can save you. Here it was the calendar integration.

The scariest part is that this came from code written in the name of safety. The routine I wrote to protect users was the one reliably destroying data. The more defensive a piece of code is, the harder you should interrogate when it fires.

The app in this post

Everything above is from building THE TASK: AI To-Do Prep (App Store, free), an iOS app I develop solo. The idea is that AI fills a written task out until it's actually actionable. The rest of the series lives in the build log.

Comments (0)

No comments yet. Be the first to leave one.