Quick answer: Fingerprint every fixed bug (stack hash, feature vector), store the fingerprint in your bug tracker, match incoming reports against it, and alert the original fixer the moment a match is found. Catch regressions on the first occurrence, not after 50.
A bug takes you two days to fix. Six weeks later, it ships again because somebody reverted the wrong file in a merge. By the time a player reports it, 20 more players have already seen it. A sticky alert would have flagged it the instant the first recurrence hit your crash backend.
The Fingerprint
A fingerprint is a deterministic key derived from the bug’s signature. For crashes, hash the normalized stack:
def crash_fingerprint(stack):
frames = [normalize(f) for f in stack[:5]]
return sha256("\n".join(frames)).hexdigest()[:16]
def normalize(frame):
# strip line numbers, inlined wrappers, anonymous lambdas
f = re.sub(r':\d+', '', frame)
f = re.sub(r'<lambda_\d+>', '<lambda>', f)
return f
For non-crash bugs (UI glitches, gameplay logic errors), combine a fixed set of context keys: scene, action, game mode, build, player OS.
Storage and Lookup
When a bug is closed, record its fingerprint and the closing commit in the bug tracker. Every incoming report gets its fingerprint computed and looked up. A hit means a possible regression.
# On new crash report
fp = crash_fingerprint(report.stack)
closed_bug = bugs.find_one({"fingerprint": fp, "status": "closed"})
if closed_bug:
raise_alert(
bug=closed_bug,
new_report=report,
assignees=[closed_bug.closed_by, closed_bug.owner_lead],
)
Alert Content
The alert should be immediately actionable:
- Bug ID and title.
- Original fix commit hash + link.
- New occurrence: player ID, build version, timestamp, stack.
- Link to reopen the bug.
Route to the original fixer and their team lead. They have the context to decide: real regression, false-positive fingerprint collision, or a different bug that happens to hit the same stack.
False Positive Rate
Stack hashing produces some collisions. A good fingerprint has collision rate under 1%. Measure by sampling alerts and checking whether each match is really the same bug. If collisions exceed 5%, widen the hash (include more frames or file paths).
Sticky Duration
Fingerprints don’t expire. A bug fixed two years ago that regresses today still matters — especially because nobody remembers it. The storage cost of keeping fingerprints forever is trivial; the benefit of catching ancient regressions is real.
“A regression alert fires when one player hits a known bug. The alternative is waiting until fifty players hit it.”
Related Issues
For broader deduplication, see how to build a crash report deduplication system. For regression budgets, see how to set up a regression budget for your game.
Fingerprint every bug you close, forever. Storage is cheap, and the first time an alert fires after two silent years you’ll be glad you did.