Session Replay
SkillDatabases & dataLets your agent inspect and troubleshoot browser session replay recordings, including console and network error diagnostics.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Session Replay skill
About this capability
Inspect, troubleshoot, and extend Analytics session replay recordings.
What this skill tells your AI
The instructions your AI receives, as published by builderio/agent-native in templates/analytics/.agents/skills/session-replay/SKILL.md and read by ahel’s review.
Use this skill when working on /sessions, replay ingest, replay storage, or
agent answers about browser recordings in the Analytics template.
Source Of Truth
- Replay ingest writes
session_recordingsandsession_replay_chunks. - The UI and agent must use
list-session-recordings,get-session-replay-summary, andget-session-replay-events. /sessions/:recordingIdis keyed bysession_recordings.id, notanalytics_events.session_id.- Do not add actions that synthesize "sessions" from
analytics_events. Events can be linked beside a recording throughsession_id, but they are not playable replay rows by themselves.
Storage And Access
- Never expose object-storage URLs or raw
session_replay_chunksrows to the browser or agent. - Playback bytes must go through scoped server helpers that check
session-recordingaccess before reading private blob refs. - SQL inline chunks are a local/dev fallback only; production should use private or encrypted blob storage.
- A local Analytics app pointed at a production database must also use the key
that encrypted those replay blobs. Set
ANALYTICS_SECRETS_ENCRYPTION_KEYin an untracked local env file; do not replace the workspace-wideBETTER_AUTH_SECRETjust to read production replay storage. - When sharing a replay with an external agent, use
create-session-replay-agent-link. It mints a two-houragent_accessURL scoped to the recording, embeds a small SSR discovery payload on/sessions/:recordingId, and advertises/api/session-replay/agent-context.jsonplus bounded/api/session-replay/agent-events.jsonand/api/session-replay/agent-diagnostics.jsonreads. - Do not make session recordings public just so an agent can inspect them. Tokenized agent links are the intended handoff path.
Console And Network Capture
- While recording, the core client (
session-replay.tsin@agent-native/core) patches console (log/info/warn/error/debug), windowerror/unhandledrejection,fetch, and XHR, and emits rrweb custom events taggedagent-native.consoleandagent-native.network. - Capture is on by default whenever session replay is enabled. Tune or disable
it with the
console/networkoptions on the session replay config; each accepts a boolean or an options object ({ maxEvents?: number });networkalso acceptscaptureErrorBodies(default true) andmaxErrorBodyLength(default 2048) to control the bounded 5xx response-body snippet. - Privacy bounds: request bodies and headers are never captured. Response
bodies are captured only as a bounded, redacted snippet for 5xx (server
error) responses, capped at
maxErrorBodyLengthchars; non-5xx and network-failure (status 0) responses never carry a body. URLs are scrubbed, messages are truncated, and recorder self-traffic (the replay ingest and tracking endpoints) is excluded. - Per-session budgets: 1000 console events and 2000 network events, with a truncation notice event once a budget is hit.
- On ingest,
deriveReplaySignalscomputes the realerrorCountfrom tagged console events plus the additivenetworkErrorCountcolumn onsession_recordings. Keep new columns additive.
Agent Diagnostics Surface
buildSessionReplayAgentContextincludes adiagnosticssection: up to 50 console entries and 50 network entries, errors/failures first, with totals and truncated flags. Agent-context instructions steer agents to diagnostics as the primary debugging signal.- The agent timeline includes
console-error/network-errormarkers; error markers are kept preferentially under the 200-marker cap. apis.diagnosticsadvertises the fuller bounded list:GET /api/session-replay/agent-diagnostics.json?id=<recordingId>&agent_access=<token>&kind=console|network|all&level=<level>&limit=<n>&offset=<n>&fromMs=<n>&toMs=<n>(limit defaults to 200, max 500). It uses the same recording-scopedagent_accesstoken as the other agent JSON APIs.offsetandfromMs/toMs(inclusive offsetMs window) enable full enumeration of a session's captured entries: page withoffset, or window withfromMs/toMsaround a timeline marker'soffsetMs. Providing any of these switches ordering to strictly chronological (no errors-first reshuffle) so pages are stable and disjoint.total/errorCount/warnCount/failedCountreflect the filtered (windowed/level/kind) population, not just the returned page, and each kind's response includeshasMorealongsidetruncatedso an agent can tell whether more entries remain. Route validation rejects negative/non-numericoffset/fromMs/toMsandfromMs > toMswith 400.
Dev Tools Panel
- The
/sessions/:recordingIdreplay player has a Dev Tools toggle that opens a panel with Console and Network tabs: filter chips, search, an error-count badge, and playback-time highlighting. - Rows expand inline under the selected line (Chrome-style). Expanding a row does not seek; use Jump to to move the playhead. Extend this panel instead of adding a separate debugging surface.
Playback Viewer
- Wait for all replay chunks (
isComplete) before constructing the rrwebReplayer. Progressive chunk publishes should only update the loading bar; rebuilding the player mid-load desyncs the scrubber and playhead. - Pass normal events to
Replayeruntouched. rrweb rebuilds them in a sandboxed iframe; pre-processing DOM, stylesheet, resource, or mutation payloads makes playback diverge from the captured page. In particular, never rewritehref,src,_cssText, CSSurl(), or Meta URLs toabout:blank; that exact remediation broke historical replay CSS in PR #2040. Handle request privacy at capture or the sandbox boundary instead of mutating stored rrweb events. Historical captures without inlined resources require live stylesheet/image/font requests for accurate rendering; the viewer accepts that fidelity tradeoff, uses rrweb's script-disabled sandbox plusreferrerpolicy="no-referrer", and must never add credentials or proxy those URLs through a privileged server. - Capture-time URL scrubbing must preserve load-bearing DOM resource attributes:
src,srcset,poster,data, andhrefonly on resource links such as stylesheets, preloads, and icons. Signed CDN query parameters are part of the resource identity; redacting them produces missing CSS, fonts, images, and oversized fallback icons. Keep scrubbing Meta/navigation URLs, anchor hrefs, and console/network diagnostics. Captured_cssTextand CSS@import/url()values must remain byte-identical. - rrweb rebuilds into an
about:srcdociframe, which inherits the Analytics document's CSP. Analytics currently sends no CSP header; if a future change adds restrictivestyle-src,font-src, orimg-srcdirectives, verify historical replays and resolve external imports/fonts at capture before blocking the recorded resource origins. Do not diagnose current font loss as CSP without checking the deployed response headers first. - Let rrweb own iframe sizing entirely via Meta / ViewportResize, and keep the
outer wrapper on the exact same raw dimensions for fit-to-stage scaling.
Player geometry and pointer coordinates are fully stock and untouched — do
not add width/aspect-ratio "recovery" heuristics or pointer-coordinate
projection. There is no such thing as a stored recording with corrupt
viewport geometry: a census of all production recordings found zero stored
widths >= 3,000px. The 2026-07 "ultra-wide replay" bugs (stages rendered
3,000–9,500px wide, frozen/teleporting cursors, giant icons) were caused
entirely by demo mode's fetch interceptor: its number redactor faked any
integer >= 1000 inside raw replay JSON at view time, corrupting Meta /
ViewportResize widths, pointer x/y coordinates, and numeric values inside
_cssTextand SVG attributes before rrweb ever saw the payload (heights below 1000 stayed real, which is why the symptom looked like a viewport problem rather than a redaction bug — two different sessions that both stored a 1,152px width read back as the same 4,491px, a deterministic salted-hash fingerprint of the redactor, not two coincidentally identical malformed recordings). This is fixed inpackages/core/src/demo/fetch-interceptor.ts: raw replay payload and manifest URLs are skipped from demo number redaction entirely, and must never be routed through it again. Do not reintroduce viewport clamping or pointer-coordinate projection in the player — they can now only corrupt genuine future recordings (for example, a real 3440x900 ultrawide browser window, or a short vertical window under 1,000px tall). - Keep rrweb's stock cursor stylesheet and its hotspot transform. During playback, hide the viewer's native pointer over Analytics' transparent click-to-pause overlay so it cannot masquerade as a frozen recorded cursor.
- Keep rrweb's recorded focus handling enabled. Focus and focus-visible state
affect menus, forms, and keyboard UX; disabling
triggerFocusmakes a valid snapshot diverge from the source page. insertStyleRulesmay suppress known toast/snackbar containers only. Never hide generic framework primitives such as[data-radix-popper-content-wrapper]: Radix dropdowns, selects, tooltips, and other real recorded product UI all share that wrapper.- Keep the realistic-fidelity purity/pass-through tests in
SessionDetailPage.spec.ts— raw event identity, raw viewport dimensions, and raw resize-state derivation (including the 3,189x885 tripwire against reintroducing a clamp) — as regression guards against reintroducing any viewport "recovery" or pointer-projection heuristic. Do not change their expectations merely to bless a new sanitizer or clamp; validate the affected replay in a browser first. An interim clamp for the exact 3,189x885 pair was also deleted once the view-time redaction root cause was proven; the earlier 3,000-3,999px band was rejected because it also catches real 3440px-wide displays. Neither the exact exception nor the band belongs in the player. - The event timeline soft-highlights the active marker, auto-scrolls it into view (pausing briefly after manual scroll), and supports search. It appears beside the player from ~880px content width upward.
- Dev Tools height is capped so the replay stage never collapses into a ribbon on short viewports; the scrubber playhead stays visually distinct from red error marker dots.
Debugging A User-Reported Bug
- Search the reporting user's email on
/sessionsto find their recordings. - Open the relevant session at
/sessions/:recordingIdand click Copy for agent to mint the two-hour tokenized link. - Paste the link to an agent. The agent fetches
/api/session-replay/agent-context.json, reads thediagnosticssection and timeline markers first, then drills intoapis.diagnostics(filtered bykind/level) andapis.eventsfor the fuller bounded lists as needed. - For human verification, open the Dev Tools panel in the replay player and jump-to-seek from the failing console or network row.
Capture Defaults
- Replay is on by default for signed-in hosted users when
VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEYorconfigureTracking({ key })is present. The default sample rate is 100% of eligible sessions. - Replay remains off when no first-party analytics key is configured, and it is
not auto-enabled on localhost/local dev. Consumers can still enable replay
directly with
configureTracking({ key, endpoint, sessionReplay: { enabled: true } }). - Apps can opt out with
configureTracking({ sessionReplay: false }). - Agent-Native templates already call
configureTracking()in their roots; hosted template deployments only need the normal Agent-Native Analytics Vite/Netlify env vars on the recorded site. - Inputs are masked by default. Page text is visible unless marked with
.an-maskordata-an-mask. - Use
.an-block,.an-ignore,data-an-block, ordata-an-ignorefor sensitive zones that should not be captured. - A definitive upload
409abandons only the conflicted replay identity and immediately starts rrweb again under a fresh per-tab id, producing a new Meta + FullSnapshot for long-lived SPA tabs. Recovery is limited to one restart until an upload succeeds so a misconfigured endpoint cannot loop; Analytics tracks the content-freesession replay upload rejectedlifecycle event so conflicts and recovery success are measurable. - Do not label an old recording "corrupt" from pointer coordinates, unknown mutation node ids, or changing Meta geometry alone. Those shapes can be legitimate with scrolling, iframes/shadow DOM, navigation, and resize. A historical-artifact notice needs a durable capture/ingest marker or another low-false-positive invariant; do not guess from playback heuristics.
Signals
- GitHub stars
- 5k
- Forks
- 448
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
session-replay- Source
- github.com/builderio/agent-native