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 · tools used 4

Just say 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 [email protected] 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.

agentry_get_distinct_id_summary {
  project_id: "default",
  distinct_id: "[email protected]",
  window_hours: 24
}
{
  "user": { "distinct_id": "[email protected]", "last_seen": 1747400400 },
  "recent_events": [
    { "event": "page_view", "ts": 1747400100, "properties": { "path": "/checkout" } },
    { "event": "checkout_started", "ts": 1747400120 },
    { "event": "checkout_failed", "ts": 1747400180, "properties": { "reason": "address_missing" } }
  ],
  "errors": [
    { "fingerprint": "f_npe_a", "message": "Cannot read properties of null (reading 'address')",
      "occurred_at": 1747400180, "session_id": "s_paddy_42" }
  ]
}
Agent

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

agentry_list_session_replays {
  project_id: "default",
  distinct_id: "[email protected]",
  window_hours: 24
}
{
  "replays": [
    {
      "session_id": "s_paddy_42",
      "started_at": 1747399980,
      "ended_at": 1747400195,
      "duration_seconds": 215,
      "events_count": 84,
      "errors_count": 1
    }
  ]
}
agentry_get_replay_snapshots {
  session_id: "s_paddy_42",
  from_ts: 1747400120,
  to_ts: 1747400180
}
{
  "snapshots": [
    { "ts": 1747400120, "type": "click", "target": "button[data-test=checkout]", "url": "/cart" },
    { "ts": 1747400122, "type": "navigation", "url": "/checkout" },
    { "ts": 1747400128, "type": "click", "target": "button[data-test=add-address]" },
    { "ts": 1747400129, "type": "dom_mutation", "summary": "modal AddressForm opened" },
    { "ts": 1747400141, "type": "input", "target": "input[name=line1]", "value_redacted": true },
    { "ts": 1747400156, "type": "click", "target": "button[data-test=save-address]" },
    { "ts": 1747400157, "type": "dom_mutation", "summary": "modal AddressForm closed" },
    { "ts": 1747400159, "type": "console", "level": "error", "text": "POST /api/address 500" },
    { "ts": 1747400164, "type": "click", "target": "button[data-test=add-address]" },
    { "ts": 1747400168, "type": "dom_mutation", "summary": "modal AddressForm opened (empty)" },
    { "ts": 1747400172, "type": "click", "target": "button[data-test=close-modal]" },
    { "ts": 1747400175, "type": "click", "target": "button[data-test=pay-now]" },
    { "ts": 1747400180, "type": "console", "level": "error", "text": "TypeError: Cannot read properties of null (reading 'address')" }
  ]
}
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 — [email protected] — 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 to be on. Default strategy is errors_only — replay only records sessions where an error occurred — which is the cheapest signal-to-cost ratio:

// Via MCP — once, at setup
agentry_configure_session_replay { 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_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/${PROJECT_ID}/`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.AGENTRY_DSN}`,
    "Content-Type": "application/json",
    "User-Agent": "myapp/1.0",  // REQUIRED
  },
  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)

Variations

  • “Pull the replays for everyone who hit f_npe_a in the last 7 days. Is the pattern always ‘address save 500’?”
  • “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.”

Try this recipe in your own agent.

Paste the prompt above into your agent. It'll set up Agentry against your data, then run the recipe and show you the real output.

Install https://agentry.sh/agentry.md and set it up