Rando.gg

A cron job put 489 ad-serving games back in our catalogue

We stored why a game was disabled in a column that only recorded who disabled it. The link checker treated its own flag as licence to re-enable, and because an ad-serving game returns 200 forever, it restored every one of them on the next scheduled run.

We run two automated screens. One checks that a game URL still works. The other loads each game in a real browser and records which ad networks it contacts. Games failing either check are switched off.

Both wrote their decision to the same boolean column, named for the fact that an automated process had made it rather than a human. That name was the bug.

The two readings

The ad screener set the flag meaning: this was turned off automatically, so do not treat it as an editorial decision.

The link checker read the same flag meaning: this was turned off automatically, so I am allowed to turn it back on if the URL recovers.

Both readings are defensible. Only one of them is written down anywhere, and it was in a comment claiming the opposite of what the code did.

Why it was total

A dead link stays dead, so the restore path almost never fires for link rot. An ad-serving game is different: it is perfectly reachable. That is the entire point. It returns 200 today, tomorrow and next year.

So every ad-retired game passed the link check on every sweep, and the restore branch fired for all of them at once.

489 gamesrestored to the live catalogue in a single scheduled run

The catalogue went from 106 games to 595 overnight, while the site continued to tell every visitor that it was screened for advertising.

The fix, and the fix we rejected

The obvious repair is to make the restore check whether ads were recorded. That is what we did. The detail worth copying is that we made the new parameter required rather than optional with a safe-looking default.

An optional parameter defaulting to false would have compiled everywhere, kept every existing caller working, and silently reintroduced the same catastrophe the first time somebody added a new one. A required parameter forces the next person to answer the question.

The flag recorded who turned it off. It never recorded why. Everything downstream had to guess, and one of the guesses was catastrophic.

What we would tell you to check

  • Any column shared by two automated processes that write for different reasons.
  • Any comment asserting a safety property that no test enforces. Ours claimed the flag prevented resurrection; it caused it.
  • Any restore or re-enable path, which by definition runs rarely and is therefore the least exercised code you own.
  • Whether a scheduled job can undo a manual decision. Ours could, and did.

Common questions

How do you stop automated jobs undoing each other?
Record the reason a record was changed, not just that it was changed automatically. Each automated retirement should have its own reason and its own guard, so a job can only reverse decisions it is competent to judge.
Why did the link checker think it should restore those games?
Because the only signal it had was a flag meaning an automated process disabled this. Restoring recovered links is correct behaviour, and the flag gave it no way to tell a recovered link from a game removed for advertising.
Should the new parameter have had a default value?
No. A default would let a future caller omit it and silently restore ad-serving games again. Making it required turns a silent data-corruption bug into a compile error.

Not feeling this one?

Play something randomOne tap, a game you haven’t played.

More notes