Crash reporting is the whole Roku SDK, and it is on as soon as
start() has run. There are three ways a report is
made.
| Mechanism | What it catches | Needs |
|---|---|---|
exit-info | Any abnormal exit of the previous launch: BrightScript crashes, out-of-memory kills, system kills | The Main() lines, Roku OS 13.0+ |
try-catch | Errors thrown in wrapped entry points, with a stack from the point of the throw | Build-step instrumentation, Roku OS 9.4+ |
captureException | Exceptions your own code catches and hands to the SDK | Nothing extra |
Detection is on the next launch
When a channel dies, Roku records why. On the next launch your Main() stores
that exit record, and the SDK turns it into a report after start(). These exit
codes are reported:
EXIT_BRIGHTSCRIPT_CRASH,EXIT_BRIGHTSCRIPT_TIMEOUT,EXIT_BRIGHTSCRIPT_STOP,EXIT_BRIGHTSCRIPT_UNK_FUNCEXIT_CHANNEL_MEM_LIMIT_FG,EXIT_CHANNEL_MEM_LIMIT_BG,EXIT_OUT_OF_MEMORY,EXIT_AM_LOWRESOURCEEXIT_SYSTEM_KILL- any other code containing
CRASH
A normal exit — the viewer pressing Home or Back — is not reported.
occurredAt and submittedAt are different times, sometimes by days.
occurredAt is when the channel exited; the report is sent when the viewer next
opens it. Order on occurredAt.
The report carries the app version of the session that exited, not the one running now, so a crash from before an update is filed against the right version.
Stack frames are often missing without instrumentation
With the exit record alone, frames come from the device’s recent console log. That log holds only 20 lines and is frequently filled with other output, so the exit is still reported — with the exit code as its type — but without a stack.
Build-step instrumentation records the exception’s backtrace at the point of the throw, so it does not depend on the console log. Use it for release and beta builds.
When instrumentation has already recorded a crash and the next launch then sees the crash exit it caused, the two are merged into one report rather than filed twice.
Report a caught exception
try
doRiskyThing()
catch e
m.global.everframe.callFunc("captureException", e)
end try
This records handled: true, fatal: false and mechanism captureException,
and does not end the channel. The report arrives with source: "error";
unhandled crashes arrive with source: "crash". A plain string is accepted in
place of an exception object.
What arrives
{
"exceptionType": "RuntimeError(&h18)",
"message": "Type Mismatch.",
"frames": [
{
"raw": "loadDetails(id As String) As Void at pkg:/components/DetailsScreen.brs(42)",
"function": "loadDetails",
"file": "pkg:/components/DetailsScreen.brs",
"line": 42
}
],
"mechanism": "try-catch",
"handled": false,
"fatal": true,
"threadName": "render",
"occurredAt": "2026-06-15T11:59:58.400Z",
"fingerprint": "9f2c1ab30d84e177",
"details": {
"context": "onItemSelected (components/DetailsScreen.brs)",
"metadata": {
"memory": {
"percent": 62,
"limitMb": 286
}
}
}
}
Frames are innermost first. details.context names the wrapped entry point
the error passed through. threadName is render, main, or the Task
component’s type. For an exit-info report, details.metadata also carries
what Roku recorded about the exit: exitCode, and when present memLimitMb,
appState, mediaPlayerState and the tail of the console log.
The report’s title is the exception type and message. It has no description,
no screenshot and no replay. What it does carry is the
breadcrumb trail, the
screen as context.route, device and app
metadata, and the user if one was set.
Delivery and retries
Reports are queued in the registry — up to 4 — and posted to the
ingest API from a Task, never from the render thread. A
network failure, a 429 or a server error is retried with a backoff that
starts at 5 seconds and doubles up to 5 minutes; a queued report survives a
restart. Any other 4xx — a bad key, a suspended organisation — drops the
report, because retrying cannot help.
Crash loops
To keep a crash loop from flooding you, the device sends a given fingerprint once per launch and at most 3 times in a rolling hour. Further reports with the same fingerprint are dropped on the device.
Grouping
fingerprint is computed on the device from the exception type and the frames,
before a report is trimmed to fit the registry. Treat it as a strong hint, not
an identity — see Error triage & releases.
Release health
On Roku OS 13.0+, the updated Main() bridge, its releaseHealthLaunch value
passed from channel code, and an enabled, configured SDK start record one
monitored launch. Development builds are excluded. Each launch
keeps the version and build it started with, including when the next launch runs
an updated channel.
The next launch reports the previous launch’s outcome. EXIT_USER_NAV completes
a launch. Only EXIT_BRIGHTSCRIPT_CRASH and EXIT_NATIVE_CRASH count as health
crashes. Memory limits, out-of-memory, system kills, timeouts, STOP, unknown
functions and other exits remain unknown; they do not lower the crash-free rate
or count as successful launches. A launch whose outcome never arrives also
remains unknown.
Health observations have their own durable queue, so detailed crash-report
throttling does not change the crash count. A recovered try-catch report or
captureException alone cannot mark a launch crashed. Roku health is anonymous:
setting a report user does not add identified users to release-health metrics.