Lesson 214: Crash Minidump Symbolicate and build_label Correlation Receipt (2026)
Direct answer: Upload symbols for each build_label, force build_label into crash metadata (or a sidecar JSON beside the minidump), symbolicate one test crash, then file crash_symbolicate_receipt_v1.json on BUILD_RECEIPT. Continues Lesson 213 telemetry—session events tell you who ran which surface; this lesson tells you which binary actually crashed.

Why this matters now (July 2026 multi-channel crash week)
July 2026 teams promote playtest, fest_public, and HTML5 lanes in the same sprint. Crash dashboards spike with identical stack hashes and no build identity—engineers chase a fix already shipped on another depot. Wednesday smoke proves boot; it does not prove symbol upload or minidump correlation.
Pair refund signal dashboard triage—players say “crash on update” while stacks show 0x00007FF… only. Lesson 212 fixes saves; this lesson fixes crash observability.
Beginner path (40-minute first symbolicate)
| Step | Action | Success check |
|---|---|---|
| 1 | Pick symbol backend | Steam / Sentry / Unity Cloud / breakpad folder |
| 2 | Upload symbols for current build_label |
Upload receipt ID logged |
| 3 | Inject build_label into crash payload |
Field visible in dashboard |
| 4 | Trigger one known test crash | Minidump or report arrives |
| 5 | Open symbolicated stack | Function names readable |
| 6 | File crash_symbolicate_receipt_v1.json |
X1–X6 pass |
Time: ~40 minutes with CI symbol step; 60 minutes first Steam + Windows depot.
Developer path (gates X1–X6)
| Gate | Check | Fail when |
|---|---|---|
| X1 | Symbol server pin per build_label |
Symbols missing for fest build |
| X2 | build_label on crash metadata |
Anonymous version=1.0.0 only |
| X3 | surface matches telemetry |
playtest crash tagged fest_public |
| X4 | Test minidump symbolicated | Hash-only stack |
| X5 | Symbol upload within 24h of promotion | Old symbols serve new EXE |
| X6 | crash_symbolicate_receipt_v1.json |
promotion_allowed: false |
Symbol server pin (X1)
Document in release-evidence/crash/SYMBOL_SERVER_PIN.json:
{
"schema": "symbol_server_pin_v1",
"build_label": "fest-demo-2026-10-rc2",
"engine": "unity_il2cpp",
"backend": "steam_crash_handler",
"symbol_upload_id": "steam-upload-2026-05-25-abc",
"executable_sha256": "sha256:…",
"uploaded_at_utc": "2026-05-25T18:00:00Z"
}
Godot / custom engines: pin breakpad or Sentry debug file path instead of Steam ID.
build_label on minidump (X2)
Unity IL2CPP: embed Application.version from same VERSION file as Lesson 201.
Sidecar pattern when SDK lacks fields:
{
"schema": "crash_context_v1",
"build_label": "fest-demo-2026-10-rc2",
"surface": "fest_public",
"minidump_id": "uuid-from-backend"
}
Upload sidecar with minidump or attach as crash annotation—never put Steam ID here.
Crosswalk with telemetry (X3)
Telemetry surface |
Crash surface |
Match? |
|---|---|---|
fest_public |
fest_public |
required |
playtest_invite |
fest_public |
fail — wrong depot |
Join dashboards on build_label + surface only (Lesson 213).
Test crash protocol (X4)
| Engine | Test crash | Pass when |
|---|---|---|
| Unity | throw new Exception("BUILD_RECEIPT_TEST_CRASH"); in dev menu |
Symbolicated BUILD_RECEIPT_TEST_CRASH |
| Godot | assert(false) behind debug flag |
File:line after upload |
| NW.js | process.crash() in internal build only |
Never in fest_public |
Strip test hook before fest promotion; receipt records test crash ID removed in production row.
crash_symbolicate_receipt_v1.json
{
"schema": "crash_symbolicate_receipt_v1",
"build_label": "fest-demo-2026-10-rc2",
"symbol_server_pin": "release-evidence/crash/SYMBOL_SERVER_PIN.json",
"test_crash": {
"triggered": true,
"symbolicated": true,
"top_frame": "BUILD_RECEIPT_TEST_CRASH",
"test_hook_removed_in_production": true
},
"metadata_fields": ["build_label", "surface"],
"paired_receipts": {
"telemetry_session": "release-evidence/telemetry/TELEMETRY_SESSION_RECEIPT.json",
"save_slot_label": "release-evidence/saves/SAVE_SLOT_LABEL_RECEIPT.json"
},
"gates": {
"X1_symbol_pin": "pass",
"X2_build_label_metadata": "pass",
"X3_surface_correlation": "pass",
"X4_test_symbolicate": "pass",
"X5_upload_freshness": "pass",
"X6_receipt": "pass"
},
"promotion_allowed": true
}
Pin under release-evidence/crash/CRASH_SYMBOLICATE_RECEIPT.json.
BUILD_RECEIPT columns
| Column | Source |
|---|---|
crash_symbolicate_receipt |
This lesson |
symbol_server_pin |
Path + upload ID |
telemetry_session_receipt |
Lesson 213 |
build_label |
VERSION spine |
Thursday row review — Crash row: symbolicated Y/N + build_label match.
Engine lanes
| Engine | Symbol artifact | Upload lane |
|---|---|---|
| Unity IL2CPP | .pdb / .so.debug |
Unity Cloud Diagnostics help |
| Godot 4.5 | Export templates debug | Self-hosted breakpad |
| GameMaker | YYC debug symbols | Steam crash dumps folder |
| Ren'Py | Traceback text only | Log build_label in log.txt |
Proof table (crash triage week)
| Reported issue | build_label in stack? | Symbolicated? | Action |
|---|---|---|---|
| Crash after fest patch | yes | yes | Fix top frame |
| Crash playtest only | playtest label | yes | Do not patch fest yet |
| Hash-only stack | no | no | Block promotion (X4) |
Key takeaways
- Unsymbolicated stacks are not triage—they are noise during fest week.
build_labelmust match upload logs and telemetry—notApplication.versionhand-typed once.- Test crash before players—proves pipeline, not gameplay.
- Symbol freshness (X5) matters more than symbol existence from last month.
- Refund dashboard (215) assumes crash rows exist—ship this receipt first.
- Multi-channel uploads multiply crash sources—
surfaceis mandatory. - Wednesday smoke does not replace symbol upload—run both.
- AI dialogue hotfix (216) needs symbolicated stacks to avoid guessing.
Prerequisites
- Lesson 213 — telemetry session receipt
- BUILD_RECEIPT beginner pipeline
- Wednesday demo smoke
- Lesson 201 — build_label spine
Common mistakes
- Uploading symbols once per project, not per
build_label. - Shipping test crash hook in fest_public.
- Correlating crashes with Steam ID instead of
build_label. - Skipping symbol upload on HTML5-only week (still file receipt n/a + desktop pin).
- Chasing playtest crashes on fest branch without surface filter.
Troubleshooting
| Symptom | Lane |
|---|---|
| Symbol upload failed | Unity symbol upload help |
| Wrong build in dashboard | X3 + Lesson 213 |
| Spike after promotion | X5 freshness + Wednesday smoke |
| “Crash” = save loss | Lesson 212 |
Bevy wasm panic without build_label |
Bevy crash log correlation preflight — separate from minidump X1–X6 |
| UE menu playtest .dmp anonymous | Unreal menu minidump build_label preflight — M1–M6 lane |
Mini exercise (60 minutes)
- Pin
SYMBOL_SERVER_PIN.jsonfor current festbuild_label. - Add
build_label+surfaceto crash SDK init. - Trigger test crash on internal build; capture symbolicated frame.
- Remove test hook; rebuild; confirm hook absent.
- File
crash_symbolicate_receipt_v1.json; update BUILD_RECEIPT.
Next: Lesson 215 — refund signal dashboard receipt.
Continuity — H1 2026 arc (212–217)
| Lesson | Receipt focus |
|---|---|
| 212 | Save slot labels |
| 213 | Telemetry session |
| 214 (this) | Crash symbolicate + build_label |
| 215 | Refund dashboard |
| 216 | AI dialogue patch |
| 217 | H1 capstone |
FAQ
Sentry instead of Steam?
Set backend: sentry in pin JSON; X1–X6 unchanged.
No desktop build (HTML5 only)?
File receipt with desktop_symbolicate: n/a; still require build_label on web error reports if captured.
Same stack on two build_labels?
Expected—use build_label to see which depot still ships the bug.
Symbolicate with build_label pinned—or fest crash threads stay unreadable hash soup.