Diagnose a bug from the session replay, not the stack

Customer reported a checkout error. Pull their session replay, read what they did in the 60s before the error. Some bugs only make sense when you see the user's screen.

Published

difficulty intermediate · time to value 2 minutes

Start from this

Customer reported a checkout error. Pull their session replay. Tell me what they did in the 60 seconds before the error and what likely caused it.

Why this matters

Some bugs only make sense once you see the user’s screen. The stack tells you what crashed. The replay tells you what the user was trying to do. The latter is often the diagnostic difference:

  • Stack: TypeError: Cannot read property 'address' of null at BillingForm.tsx:118
  • Replay: The customer clicked “Add address” 3 times, each time the modal closed without saving. They eventually gave up and clicked “Pay anyway” — boom, null address.

Now you know it’s a modal bug, not a billing-form bug. The fix isn’t a null-guard on line 118 — it’s making the address modal actually save.

Agentry’s replay strategies (off, errors_only, sampled, url_scoped, all) plus the per-distinct_id retrieval means you can leave replay off most of the time, only triggered around errors, and still get full context for the cases that matter.

What you get

  • The customer’s most recent session that contained an error
  • The 60-second pre-error timeline (clicks, navigations, form changes)
  • A diagnostic narrative — agent reads the snapshots, tells you what likely went wrong
  • A reproducible test case for the engineer to fix against

Walk through it

You

Customer paddy@acme.com reported a checkout error this morning. Pull their session replay. Tell me what they did in the 60 seconds before the error and what likely caused it.

Agent

Three steps: find their recent error, find the replay that contains it, read the snapshots around the error timestamp.

GET /v1/projects/:project_id/users/paddy%40acme.com/summary
Authorization: Bearer $AGENTRY_PRIVATE_API_KEY
{
  "distinct_id": "paddy@acme.com",
  "event_stats": {
    "count": 17,
    "first_seen": "2026-05-16T16:50:00Z",
    "last_seen": "2026-05-16T17:13:05Z"
  },
  "recent_events": [
    { "event": "page_view", "timestamp": "2026-05-16T17:12:00Z" },
    { "event": "checkout_started", "timestamp": "2026-05-16T17:12:20Z" },
    { "event": "checkout_failed", "timestamp": "2026-05-16T17:13:00Z" }
  ],
  "recent_recordings": [
    { "id": "s_paddy_42", "started_at": "2026-05-16T17:09:45Z" }
  ]
}
Agent

Error at 17:13:00 in session s_paddy_42. Let me fetch the replay.

GET /v1/projects/:project_id/session-replays?distinct_id=paddy%40acme.com&date_from=2026-05-16T16:45:00Z&date_to=2026-05-16T17:20:00Z
Authorization: Bearer $AGENTRY_PRIVATE_API_KEY
{
  "recordings": [
    {
      "id": "s_paddy_42",
      "distinct_id": "paddy@acme.com",
      "started_at": "2026-05-16T17:09:45Z"
    }
  ],
  "has_next": false
}
GET /v1/projects/:project_id/session-replays/s_paddy_42/snapshots
Authorization: Bearer $AGENTRY_PRIVATE_API_KEY
{
  "snapshots": [
    { "type": 2, "timestamp": 1778951340000 },
    { "type": 3, "timestamp": 1778951356000, "data": { "source": 2 } },
    { "type": 3, "timestamp": 1778951379000, "data": { "source": 6 } }
  ],
  "next_action": "Snapshots are rrweb-format DOM events..."
}
Agent

Here’s what really happened:

  1. 17:12:00 — paddy clicked checkout
  2. 17:12:08 — clicked “Add address,” modal opened
  3. 17:12:21 — filled in the address (line1 was the only field shown — that’s a clue)
  4. 17:12:36 — clicked “Save address.” Modal closed without confirmation.
  5. 17:12:39 — but a console.error fired: POST /api/address 500the save actually failed silently
  6. 17:12:44 — paddy thought “huh, did it save?” and clicked “Add address” again
  7. 17:12:48 — modal opened again, but empty (because nothing was saved)
  8. 17:12:52 — paddy closed the modal and clicked “Pay now,” figuring he’d handle billing after
  9. 17:13:00 — TypeError, because billing relies on address being set

The bug is NOT in BillingForm.tsx:118. It’s that the POST /api/address endpoint returned 500 and the front-end swallowed it — closed the modal anyway, no user-visible error. The TypeError that fired 21 seconds later is a downstream symptom.

Real fix: in the modal save handler, check the response status and show an error toast on non-2xx. Bonus: figure out why /api/address 500’d (separate investigation — probably a validation issue with paddy’s specific input).

Want me to write a regression test that exercises the “address save fails + user proceeds to pay” path?

The output

Session replay diagnosis — paddy@acme.com — session s_paddy_42

Pre-error timeline (60s):
  17:12:00  click   [checkout]
  17:12:08  click   [add-address]
  17:12:08  modal   AddressForm opened
  17:12:21  input   address line1 filled
  17:12:36  click   [save-address]
  17:12:36  modal   AddressForm closed
  17:12:39  CONSOLE POST /api/address 500   ★ silent failure!
  17:12:44  click   [add-address]                (user retried)
  17:12:48  modal   AddressForm opened (empty)   (because nothing saved)
  17:12:52  click   [close-modal]
  17:12:55  click   [pay-now]                    (gave up)
  17:13:00  ERROR   TypeError null.address       ← stack tells you this
                                                 ← replay tells you it's a SYMPTOM

True root cause: AddressForm.tsx silently swallows the 500 from /api/address.
                 BillingForm.tsx:118 null-check is the wrong fix.
                 The real fix is in the save handler.

Suggested:
  1. AddressForm: show error toast on non-2xx response, don't close modal
  2. Investigate why /api/address returned 500 (separate case)
  3. Write a regression test: "save fails → user proceeds → expect blocking error"

Setting it up

Replay needs two pieces:

  1. Turn on the project’s replay strategy in Agentry/PostHog.
  2. Load the browser recorder (posthog-js) on the pages you want to inspect.

Default strategy is errors_only — replay only records sessions where an error occurred — which is the cheapest signal-to-cost ratio:

// Once, at setup
// Endpoint: POST /v1/projects/:project_id/posthog/session-replay/configure
// Exact request shape: fetch the PostHog tag from /v1/openapi.json before sending.
// Concept field: strategy = "errors_only"

Or via raw HTTP:

await fetch(
  `https://api.agentry.sh/v1/projects/${PROJECT_ID}/posthog/session-replay/configure`,
  {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.AGENTRY_PRIVATE_API_KEY}`,
      "Content-Type": "application/json",
      "User-Agent": "config/1.0",  // REQUIRED — Cloudflare 403s default UAs
    },
    body: JSON.stringify({ strategy: "errors_only" }),
  },
);

On the client side, your fetch helper sends session_id alongside each event:

// At app boot
const sessionId = crypto.randomUUID();
window.AGENTRY_SESSION_ID = sessionId;

// Each event includes it
await fetch(`https://api.agentry.sh/v1/analytics/`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.AGENTRY_PUBLIC_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    event: "checkout_started",
    distinct_id: user.email,
    session_id: window.AGENTRY_SESSION_ID,
    properties: { /* ... */ },
  }),
});

When the recording strategy is errors_only, the client buffers DOM events locally and only ships them up after a logged error in the same session. Privacy-friendly + bandwidth-friendly.

Other strategies:

  • off — no replay
  • sampled — fixed % of all sessions
  • url_scoped — only specific URL patterns (e.g. /checkout/*)
  • all — record everything (use during a specific bug hunt, then revert)

For the Agentry website itself, the recorder is wired in apps/web/src/lib/agentry-client.ts. The browser only needs the same PUBLIC_AGENTRY_PUBLIC_API_KEY used for /v1/logs/ and /v1/analytics/; the helper fetches replay bootstrap config from GET /v1/analytics/browser-config.

The WorkOS-hosted sign-in page cannot be replayed because Agentry’s JavaScript does not run on api.workos.com / WorkOS-hosted domains. Record the Agentry pages before and after the hosted redirect, then correlate by distinct_id, $session_id, and the signup events.

Weekly onboarding drop-off review

For prompts like “fetch my session replays that get to onboarding but don’t finish it and analyze them once a week”, use a scheduled agent or cron job. Agentry provides the API; the schedule runs wherever you run automation.

For Agentry’s own prompt-to-signup funnel, use:

  • entry_event: install_prompt_copied
  • completion_event: signup_completed

signup_started fires immediately before redirecting to WorkOS; signup_completed fires in the API after the device code is authorized. The browser helper mirrors the anonymous id and session id into cookies so those server-side events can be joined back to the pre-signup replay candidate.

First, get a bounded candidate set:

POST /v1/projects/:project_id/query-blueprints/funnel_dropoff_users/run
Authorization: Bearer $AGENTRY_PRIVATE_API_KEY
Content-Type: application/json

{
  "params": {
    "entry_event": "onboarding_started",
    "completion_event": "onboarding_completed",
    "days": 7,
    "limit": 25
  }
}

Then, for each returned distinct_id, fetch recordings around entry_at:

GET /v1/projects/:project_id/session-replays?distinct_id=user_123&date_from=2026-06-15T00:00:00Z&date_to=2026-06-22T00:00:00Z
Authorization: Bearer $AGENTRY_PRIVATE_API_KEY

If the drop-off row includes session_recording_id, fetch that replay directly:

GET /v1/projects/:project_id/session-replays/:replay_id/snapshots
Authorization: Bearer $AGENTRY_PRIVATE_API_KEY

The weekly job should summarize patterns across the sampled replays: repeated clicks, navigation loops, missing form validation, console errors, or places where users abandon after the same UI state. Agentry’s /v1/docs/automation documents the cron-style weekly digest pattern; no webhook is needed for a weekly pull.

Variations

  • “Pull the replays for everyone who hit f_npe_a in the last 7 days. Is the pattern always ‘address save 500’?”
  • “Every Monday, sample 25 users who fired onboarding_started but not onboarding_completed, fetch their replays, and summarize the top three friction patterns.”
  • “Filter to mobile-Safari only — replays for the iOS users hitting this. Is the error platform-specific?”
  • “Find sessions where the user clicked the same button 3+ times in 10s — those are usually rage clicks indicating broken UX.”
  • “Compare 2 replays from different users hitting the same error — find the common DOM state.”

Adapt this playbook in your own agent.

Ask your agent to map the starter prompt to your saved signal map, live events, and the current HTTP API before answering.

Set up Agentry in this repo by following https://agentry.sh/install.md. I authorize you to: run its device-code login and save the key to ~/.agentry/credentials.json, read this repo, propose an instrumentation plan for my approval, and edit code after I approve it.

+ Full access
5.5 Extra High
  1. 1. Open your repo in Codex, Claude Code, Cursor etc.
  2. 2. Paste the install prompt.
  3. 3. Your agent reads the install doc and shows you an implementation plan for approval.