Skip to content

fix: fingerprint minidump crashes by crash site for Error Tracking - #20

Merged
Fiona2016 merged 2 commits into
publishfrom
fix/crash-fingerprint
Aug 10, 2026
Merged

fix: fingerprint minidump crashes by crash site for Error Tracking#20
Fiona2016 merged 2 commits into
publishfrom
fix/crash-fingerprint

Conversation

@Fiona2016

Copy link
Copy Markdown
Collaborator

Summary

  • Minidump crash events now carry error.fingerprint, computed as {exceptionType}|{module basename}|{normalized module offset} from the first non-system frame of the crashed thread (e.g. SIGSEGV|MyApp|0x12ab3c).
  • Without it, the backend groups every crash of one exception type into a single issue: all minidump crashes share the constant message Application crashed, so both fingerprint grouping and similarity grouping collapse distinct crash sites together.
  • No backend change needed: the intake already stores error.fingerprint verbatim, grouping prefers an event-provided fingerprint, and similarity grouping is skipped when one is present (fc-rum logic/issue/fingerprint.go, group.go).

Details

  • The offset, not the instruction address, identifies the crash site: ASLR rebases modules on every launch, while an offset is stable across runs of the same build. Offsets drift between builds, so a new app version opens fresh issues — the same trade-off as Android NDK top-frame grouping.
  • Offsets are normalized (lowercase, leading zeros stripped, BigInt-exact) so equivalent spellings group together.
  • Dumps without an identified crashed thread carry no fingerprint (and no stack); the backend's frames==0 gate already keeps them out of issue grouping.
  • Assembly/transport needed no change: main-process events deep-merge all error fields and the batch transport serializes the event verbatim; only the RawRumError inline type needed the field added.
  • CHANGELOG gets a [0.2.1] section, so the release workflow's notes extraction has something to find.

Review

Code-review pass performed against origin/publish before opening: no blocking findings.

Verification

  • yarn vitest run src/domain/rum/error/CrashCollection.spec.ts — 31/31 pass (7 new: frame selection, system-frame skip, all-system fallback, no-fingerprint cases, offset-spelling equivalence)
  • yarn test:unit — 696/696 pass
  • yarn typecheck — clean
  • yarn format — clean on changed files

Notes / Risks

The note claimed the backend's zero-frame gate keeps these events out of
issue grouping. It no longer does: an exception-less crash now reaches Error
Tracking and groups by exception type and message.

The behaviour here is unchanged and still right — with no exception stream
nothing records which thread died, so there is no site to fingerprint, and
building one from an arbitrary thread would invent a crash site the dump does
not contain. Only the stated consequence was wrong, and it is the kind of
mistake that outlives the reader who could catch it.
@Fiona2016

Copy link
Copy Markdown
Collaborator Author

Reviewed — the approach and the implementation both hold up. Pushed one commit that corrects a comment; no behaviour change.

Checked and correct

  • The site is keyed on module_offset, not an instruction or base address. Measured across three runs of the same crash: the module base moved every launch (ASLR) while the offset stayed put, so an address-based key would have opened a new issue on every crash.
  • exceptionType is part of the key, so two different faults at one site stay apart.
  • isSystemModule is given frame.module, which is the module's full path — formatFrameStack matches it against module.code_file, so the system-path checks actually fire. Test fixtures use real paths (/usr/lib/libSystem.B.dylib, /System/Library/...), so the skip-system-frame branch is genuinely exercised rather than passing by accident.
  • Offset normalization is a good catch I had missed: equivalent spellings of one offset would otherwise split into separate issues, and BigInt keeps it exact past 2^53.
  • Spreading the field rather than assigning it avoids an explicit undefined becoming an own property — the intake treats a present fingerprint as authoritative.

Verified end to end, since a fingerprint the backend never reads would be inert: the intake does store error.fingerprint on the error row, truncating at 255, and the column is VARCHAR(255). The longest realistic key here (EXC_BAD_ACCESS / KERN_INVALID_ADDRESS|Electron Framework|0x29e4bb4, ~66 chars) is well inside that.

The one fix: the note said the backend's zero-frame gate keeps a site-less crash out of issue grouping. That stopped being true earlier today — such a crash now reaches Error Tracking and groups by exception type and message. The code was already doing the right thing; only the stated consequence was stale.

Tests: 31 pass, typecheck clean.

@Fiona2016
Fiona2016 merged commit 78f098f into publish Aug 10, 2026
2 checks passed
@Fiona2016 Fiona2016 mentioned this pull request Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant