Release health and adoption
Errors → Releases compares explicitly instrumented executions within the current application and environment. It does not infer session health from analytics inactivity, error-event volume or missing crash reports.
Browser setup
import { spm } from "@saaspro/browser";
import { startReleaseHealth } from "@saaspro/browser/release-health";
spm.init({ key: "YOUR_PUBLIC_KEY", release: "web@1.2.0", consent: "pending" });
const health = startReleaseHealth(spm);
// Invoke from your existing consent decision.
spm.consent("granted");
The optional subpath is separate from the core browser bundle. Install it immediately after initialization, before application work. A health session starts after persistent analytics consent and a nonempty release are available. Ephemeral mode, pending/revoked consent and opt-out do not report. A session is one document execution while the configured analytics session and release remain stable; reload, session reset, release change and a page restored from the back/forward cache begin new health sessions. Consent withdrawal aborts transport and drops local state without inventing a successful end.
autoErrors must remain enabled for automatic window errors and unhandled promise rejections. Captured errors with handled: false increment the health snapshot after the browser's before hook and queue bounds. Handled exceptions and events removed by that hook are excluded. Health counts do not depend on the server's ordinary error-event sampling. Installing late, disabling auto capture, filtering errors or losing transport can reduce coverage.
The helper sends a start, a snapshot after a captured unhandled error, and a heartbeat each minute while visible. Page departure sends a final completion snapshot by beacon, with keepalive fallback. Hiding a page alone does not prove completion. Beacon acceptance by the browser does not prove server receipt. await health.flush() returns whether an HTTP report was accepted; onDelivery is an optional callback. health.stop() tears down the helper without claiming completion. Repeated installation on the same browser client returns the existing helper.
Node setup
import { startHealthSession } from "@saaspro/node/release-health";
const health = startHealthSession({
key: process.env.SPM_SECRET_KEY!,
host: process.env.SPM_HOST,
release: "worker@1.2.0",
});
try {
await runJob();
} catch (error) {
await health.reportUnhandled();
throw error;
} finally {
await health.complete();
}
Define the same execution boundary for each compared release: one job, request, or explicitly managed session. Use heartbeat() at least once per minute for long work and complete() after it finishes. reportUnhandled() records the failure count; capture the error with the main SDK separately to obtain issue details. Return values report actual HTTP acceptance. Both helpers keep at most one request in flight and one cumulative pending snapshot; calls waiting for a coalesced snapshot share its delivery result. Stop or an identity reset settles discarded reports as unaccepted. The Node host must use HTTPS outside loopback, excludes URL credentials/query/fragment, and transport refuses redirects. stop() aborts without completion. No process-exit hook, native crash detector, offline disk queue or crash-dump capture is implied. Server process termination can leave an unknown outcome.
Interpret the report
- With unhandled errors: at least one captured unhandled error, whether or not completion was received.
- Completed without captured errors: a received completion snapshot with zero captured unhandled errors. This is an observed lifecycle outcome, not proof of crash freedom.
- Still reporting: no captured unhandled errors, no completion, and a report within five minutes.
- Outcome unknown: no captured unhandled errors or completion, and no report for five minutes. Browser suspension, crashes, lost delivery and deliberate stopping can all produce this state.
- Observed failure rate: known failed sessions divided by all reported sessions, shown as at least a percentage. Unknown outcomes may conceal more failures. Never subtract this number from 100% to claim crash-free sessions.
- Outcome coverage: known failed sessions plus completed sessions without captured errors, divided by all reported sessions.
- Adoption: a release's share of instrumented sessions on the same platform in the selected start-time window. It is not install penetration, unique users or all application traffic.
A baseline comparison uses the same platform and period. Percentage-point differences are descriptive lower-bound comparisons, not significance tests or proof that a deployment caused a regression. Missing baseline platforms stay unavailable. Release filtering preserves the same-platform adoption denominator. The outcomes summary deliberately retains the full selected platform scope.
Investigate issues opens the existing release filter, including handled errors and all retained issue dates. Issue event sampling, error filters and reporter installation can make issue totals differ from health counts. Build artifacts use the same release convention; debug IDs remain authoritative for symbolication.
HTTP collector
POST /api/v1/ingest/health accepts public or secret ingest keys. Public keys require an allowed, non-null Origin; bot traffic is refused. Management tokens cannot write health snapshots. Authentication determines organization, app and environment; callers cannot set them in the payload. The errors module must be enabled. Public keys cannot read reports or submit IP/user-agent context.
{
"key": "YOUR_INGEST_KEY",
"session": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"release": "web@1.2.0",
"platform": "web",
"startedAt": "2026-09-08T00:00:00.000Z",
"sequence": 1,
"durationMs": 0,
"unhandledErrors": 0,
"state": "active"
}
}
Secret keys may use Authorization: Bearer … instead of the body key. Platforms are web, node, python, ios, android and other; these are caller-declared instrumentation labels. Native/platform adapters must define and report real execution boundaries themselves. Release is 1–128 characters, sequence 1–10,000, cumulative errors 0–10,000 and duration at most 24 hours. Snapshot bodies are closed and limited to 4 KiB. Starts must arrive within 24 hours and may be no more than one minute ahead of server time; reported duration cannot extend beyond that tolerance.
Every snapshot contains the complete cumulative state, so a later snapshot can recover a lost start. Session IDs must be unpredictable UUIDs unique per execution; the server stores only their hashes. Release, platform, start time and ingest-key ownership cannot change. Lower/equal sequence retries are acknowledged without updating counters or freshness. Increasing sequences must not decrease duration/errors; completion is terminal. Retry the identical final snapshot after an uncertain response. HTTP 202 with accepted: true confirms storage; 400 indicates invalid data, 401/403 authentication/origin/module/pause refusal, 409 conflicting or deleted session, and 429 rate/storage budget refusal. No silent sample extrapolation applies.
Limits, retention and access
Defaults are enabled for explicitly installed helpers, 10,000 new sessions/day and 30-day retention. Collection and setup can pause reporting, adjust retention to 1–90 days and the daily limit to 1–100,000. App retention caps this immediately. There is a fixed 100,000-row retained session limit per environment; data/index overhead is additional. A per-environment advisory lock serializes admission and settings. Existing sessions may finish after a daily/storage cap; new-session refusals are counted as requests, so retries can repeat that count. Counts and adoption can be biased when instrumentation or budgets differ.
Reports require errors.read; settings and deletion require errors.write, current membership, same-origin/CSRF checks and a passkey verified within ten minutes. Mutations append audit events atomically. No person, email, IP, URL, error message or raw client session ID is stored in health tables. The report needs no PII permission.
The errors.health.purge worker processes 50 environments every five minutes and removes up to 5,000 expired sessions per environment. It retains 91 days of daily usage. Deleting health data preserves daily usage and records a cutoff that rejects old-session retries. New executions can report after the deletion. Report queries use a 12-second statement budget, a 90-day maximum, 25 release/platform groups per page and at most 1,000 daily release groups in the API time series (with truncation indicated).
GET /api/v1/apps/{app}/errors/releases?env=production&days=7&platform=web&baseline=web%401.1.0 provides the same tenant-scoped aggregate evidence through a Management API token with errors.read. Optional release filters the comparison rows and daily series; platform filtering applies to all results. Local verification is separate from package publication, deployment and customer instrumentation.