Quick answer: GameMaker desktop crashes come in two flavors: GML runtime errors the runner reports as a fatal exception, and harder native faults inside the YYC or VM runner. Catch the first with exception_unhandled_handler, log the second from the platform, and ship both with the build, platform, and game state attached so you can group duplicates and fix the real cause.
GameMaker Studio makes it easy to ship a desktop game to Windows, Mac, and Linux, but it does not make crashes easy to see once players are running your build. A GML error that pops the runner's error dialog on your machine becomes a silent close on theirs, and a native fault in the YYC compiled runner leaves almost nothing behind. This post walks through the crash surfaces that actually matter for a GameMaker desktop title, how the VM and YYC runners differ, and how to collect enough context that each report points at one fixable bug.
Where GameMaker desktop games actually crash
Most reproducible failures in a GameMaker game are GML runtime errors: a variable read before it was set, an array index past its bounds, a divide by zero, or a call into a ds_map that was already destroyed. The runner raises these as a fatal error and, by default, shows a dialog with the object, event, and line. On a player machine that dialog is often dismissed in a panic, so the detail you need to fix the bug is lost unless you capture it yourself.
The second category is native: an access violation inside the runner, a graphics driver fault, or a stack overflow from runaway recursion in a script. These do not raise a clean GML error because the C runtime itself has fallen over. They are rarer but nastier, and they need a platform level catch rather than a GML handler. Knowing which class a report belongs to is the first triage decision you make.
VM versus YYC and how it changes your stack traces
GameMaker ships two runners. The VM runner interprets your compiled bytecode and tends to give you cleaner GML level error text, including the script name and line, which is gold for debugging. The YYC runner transpiles your GML to C++ and compiles it, which is faster at runtime but produces native stack frames that mention the generated C++ functions rather than your original script names. A crash that reads clearly under VM can look opaque under YYC.
Because of this, it is worth shipping or at least testing a VM build alongside your YYC release build. When a YYC crash report comes in with a thin native frame, reproducing it in the VM runner often surfaces the underlying GML error with a real line number. Record which runner produced each report so you never waste time matching a YYC trace against VM expectations or the other way around.
Catching GML errors before the dialog steals them
GameMaker gives you exception_unhandled_handler, which registers a callback that fires when an unhandled GML exception would otherwise show the fatal dialog. Inside that callback you receive a struct with message, longMessage, script, line number, and a stacktrace array. That is the single most valuable hook in the whole engine for reporting, because it runs at the exact moment your game state is still intact, before the runner tears everything down.
Inside the handler, assemble a report: the long message, the stacktrace, the current room, the active object, and a few of your own gameplay variables. Then hand it to your reporting layer and decide whether to continue or quit gracefully. Keep the handler small and defensive, because code that throws inside an exception handler can mask the original error entirely and leave you debugging the wrong fault.
Capturing native faults and packaging context
For native faults the GML handler never fires, so you need a flush-on-exit strategy. Write a small breadcrumb log to disk as the game runs, recording room changes, major actions, and the last few frames of activity. After a hard crash the player relaunches and your game finds an unflushed log from the previous session, which you can submit as a probable native crash. It is not a full stack trace, but the breadcrumb trail usually names the room and action that triggered the fault.
Whatever the crash class, attach the same context envelope: game version, GameMaker runtime version, runner type, OS and version, GPU vendor and driver, display resolution, and a hash of the build. Add player-specific fields such as the current save slot or difficulty. This envelope is what lets you tell a Windows-only graphics fault apart from a logic bug that hits every platform, and it makes each report self-contained when you read it weeks later.
Setting it up with Bugnet
Bugnet gives you an in-game report button and an SDK endpoint you call from your exception_unhandled_handler. When a GML error fires, you forward the long message, the stacktrace array, the room, the object, and your custom gameplay fields in one call, and the crash lands in your dashboard with the platform context already attached. For native faults you submit the breadcrumb log the same way on next launch, so both crash classes flow into a single place instead of being scattered across forum posts and Discord screenshots.
Occurrence grouping is what makes this manageable at scale. Bugnet folds duplicate reports of the same GML error into one issue with a running count, so a single off-by-one that thousands of players hit shows as one prioritized item rather than thousands of tickets. Custom fields like runner type, OS, and GPU driver become filters, so you can confirm in seconds whether a spike is YYC-only or Linux-only and aim your fix at the right build.
Building a crash culture for a small team
Treat every shipped build as something you watch rather than something you forget. Before a release, deliberately trigger a test GML error and a controlled native fault to confirm both reporting paths still work after engine upgrades, because GameMaker runtime updates occasionally change exception behavior. A reporting pipeline that silently broke two releases ago is worse than none, since it gives you false confidence that quiet means stable.
Once reports flow, sort by occurrence count and fix the loudest crashes first, then watch the count fall after each patch as confirmation the fix landed. Over a few releases this turns crash reporting from a fire drill into a steady feedback loop, and your GameMaker desktop game gets measurably more stable for the players who never bothered to write you a message about it.
Catch GML errors in exception_unhandled_handler and native faults with a breadcrumb log, then attach runner and OS context so duplicates group and the real cause is obvious.