Skip to content
Everframe Docs
Documentation

Crash reporting

A Roku channel that has crashed cannot report anything. The next launch does it instead.

Updated

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.

MechanismWhat it catchesNeeds
exit-infoAny abnormal exit of the previous launch: BrightScript crashes, out-of-memory kills, system killsThe Main() lines, Roku OS 13.0+
try-catchErrors thrown in wrapped entry points, with a stack from the point of the throwBuild-step instrumentation, Roku OS 9.4+
captureExceptionExceptions your own code catches and hands to the SDKNothing 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_FUNC
  • EXIT_CHANNEL_MEM_LIMIT_FG, EXIT_CHANNEL_MEM_LIMIT_BG, EXIT_OUT_OF_MEMORY, EXIT_AM_LOWRESOURCE
  • EXIT_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.

See how release health is measured.