Sentry projection
The runtime can stream its failures to a report sink. Frond.Diagnostics.createRuntimeReportSink projects each runtime failure into a FrondErrorReport, whose fields line up with Sentry’s captureException scope.
Projection matters because Frond failures carry wrapper frames and cause chains. Reporting the raw thrown error often makes unrelated failures look identical — for example many different driver failures can appear as the same operation or readiness wrapper. It can also hide the actual root error behind a shallow cause chain. See Error projection for the model.
The sink
import * as Frond from "@frondruntime/core";
import * as Sentry from "@sentry/browser";
export const runtime = Frond.createRuntime({
sinks: [
Frond.Diagnostics.createRuntimeReportSink({
name: "sentry",
handleReport: ({ report }) => {
Sentry.captureException(report.error, {
fingerprint: [...report.fingerprint],
tags: report.tags,
contexts: report.contexts,
extra: report.extra,
});
},
}),
],
});
handleReport runs once per failure in a runtime event. The report is a FrondErrorReport:
| Field | Maps to |
|---|---|
error | The Error passed to captureException. |
message | A readable summary. |
fingerprint | Sentry grouping fingerprint. |
tags | Sentry tags (node id, tag, kind). |
contexts, extra | Sentry contexts and extra data. |
The sink fires only on failures — readiness errors, operation failures, dependency failures. Healthy events do not call it.
Failures in React
The sink covers failures anywhere in the runtime. For a failure that surfaced to a React error boundary, getErrorReport(error) gives you the same shape to render in the fallback. See Suspense and errors.
import { getErrorReport } from "@frondruntime/react";
function Fallback({ error }: { error: unknown }) {
const report = getErrorReport(error);
return <p>{report.headline}</p>;
}
If that boundary also reports to Sentry, capture report.diagnostic.error with report.diagnostic.fingerprint, tags, contexts, and extra. Do not capture the raw boundary error directly; that value is the React control-flow wrapper, not the tracker projection.
Operation failures in UI state
Action and refresh failures do not throw to a React error boundary. They leave the node ready and appear as operationFailure on useNodeState. Project operationFailure.error before reporting it manually.
import * as Frond from "@frondruntime/core";
import * as Sentry from "@sentry/browser";
import { useNodeState } from "@frondruntime/react";
function ProfileStatus({ userId }: { userId: string }) {
const state = useNodeState(ProfileNode, { userId });
function reportLastOperationFailure() {
if (!state.operationFailure) return;
const report = Frond.Diagnostics.createErrorReport(state.operationFailure.error);
Sentry.captureException(report.error, {
fingerprint: [...report.fingerprint],
tags: report.tags,
contexts: report.contexts,
extra: report.extra,
});
}
return (
<button disabled={!state.operationFailure} onClick={reportLastOperationFailure}>
Report last operation failure
</button>
);
}
Use the same projection rule for both surfaces: boundary errors use getErrorReport(error), stored operation failures use Frond.Diagnostics.createErrorReport(operationFailure.error).
Why a sink, not a try/catch
Failures come from many paths — an acquire rejection, a dependency that never readied, an action timeout. Routing them through the runtime sink means every failure reports the same way, with the node id and tag already attached, without wrapping each call site. See Tags and diagnostics.
Next: Pagination.