Session Recorder → An example report

Example

What an LLM-ready bug report actually looks like

“LLM-ready” is easy to claim and hard to verify. Here is the real artifact: a complete, sanitized report.md from a recorded session, exactly as an AI coding agent receives it.

Session Recorder is a free, open-source, local-first Chrome extension for recording web bugs for AI coding agents. Every recording exports as a zip whose entry point is report.md: one chronological narrative joining interactions, network requests and responses, console errors, screenshots, voice narration, and annotations on a single [mm:ss] clock.

The bug, in one line

A user places an order with an expired coupon. The API returns 500, the confirmation screen throws, and the UI shows success anyway. Here is the causal chain the recording preserves:

[00:06] CLICK "Checkout"
    ↓
[00:17] POST /api/checkout
    ↓
[00:17] 500  { error: "coupon_expired" }
    ↓
[00:18] TypeError: cannot read 'orderId' of undefined
    ↓
[00:24] annotated screenshot: "still says success"
    ↓
[00:26] spoken note: "the cart still has both items"
    ↓
        report.md → agent

No single screenshot contains that chain. No stack trace contains it either. The ordering is the diagnosis.

What is in the export

checkout-500/
  report.md          # the agent's entry point; self-sufficient at Compact/Minimal
  session.json       # the same trimmed event stream, machine-readable
  MANIFEST.md        # index of every included asset (path, kind, size)
  transcript.json    # voice segments with timestamps
  openapi.json       # optional: API spec compiled from observed requests
  screenshots/
    004-0024.jpg     # {seq}-{mmss}.jpg — sorts in capture order, joins the clock
  network/
    007-api.example.com-checkout.json   # full request/response
  audio/
    001.webm
  files/
    receipt.pdf      # files you uploaded to the app during the session

report.md — exported at Compact (L2)

Compact targets roughly 50k tokens. JSON bodies are reduced to a shape summary, repeated identical requests collapse to one line, and screenshots are kept for errors and annotations. Everything below is verbatim renderer output; only the domain, email, and IDs are fictional.

# Session Report: checkout-500

- **App:** https://app.example.com
- **Date:** 2026-08-14T16:22:08.114Z
- **Duration:** 01:47
- **Verbosity level:** 2
- **Events:** 68
- **API spec:** `openapi.json` (9 endpoint(s) compiled from captured requests)

### Tabs

| Tab | Role | URL | Title |
| --- | --- | --- | --- |
| 412 | primary | https://app.example.com/cart | Cart — Example |

## Timeline

## [00:00] NAV https://app.example.com/cart — Cart — Example

> 🎙️ [00:05] _(while clicking "Checkout")_ Two items in the cart, the total
> should be forty-two dollars.

[00:06] CLICK "Checkout" (button#checkout, tab 412)

[00:06] 🌐 200 GET /api/cart
- response shape: `{ items: Array(2) of { sku, qty, price }, subtotal, currency }`

## [00:07] ROUTE (pushState) https://app.example.com/checkout — Checkout — Example

[00:09] INPUT "ada@example.com" into (input#email, tab 412)
[00:12] INPUT "SPRING24" into (input#coupon, tab 412)
[00:14] INPUT «redacted» into (input#card-number, tab 412)
[00:16] SELECT "Total $42.00" (div.summary__total, tab 412)
[00:17] CLICK "Place order" (button.primary, tab 412)

[00:17] 🌐 500 POST /api/checkout
- request shape: `{ cartId, email, couponCode, paymentToken }`
- response shape: `{ error, requestId }`

⚠ **ERROR [00:18]** (page) TypeError: Cannot read properties of undefined
(reading 'orderId')
```
    at renderConfirmation (checkout.tsx:214:19)
    at onSubmit (checkout.tsx:181:7)
```

[00:18] console.error @ https://app.example.com/assets/checkout-8f21.js:1
```
Checkout failed: 500 {"error":"coupon_expired","requestId":"req_8a41c2"}
```

[00:18] 📷 confirmation screen after the failed request
![confirmation screen after the failed request](screenshots/003-0018.jpg)

> ## 📌 MARKER [00:19]: this is the bug

### [00:24] Annotation
- **arrow** #ff5a4d on div.summary__total ("Total $42.00")
- **text** #ff5a4d "still says success"
![](screenshots/004-0024.jpg)

> 🎙️ [00:26] It told me the order went through, but nothing happened — the
> cart still has both items in it.

[00:31] 🌐 200 GET /api/cart
> (×4 similar…) statuses: 200, 200, 200, 200

## [01:02] NAV https://app.example.com/orders — Your orders — Example

[01:05] 🌐 200 GET /api/orders
- response shape: `{ orders: Array(0) }`

> ## 📝 NOTE [01:12]
> The orders list is empty, so the order really was not created. The UI is
> lying about it.

[01:19] 🎬 VIDEO segment 00:00–01:19 → `video/001.webm`

## Appendix: Network Index

- [00:06] 200 GET https://app.example.com/api/cart
- [00:17] 500 POST https://app.example.com/api/checkout
- [00:31] 200 GET https://app.example.com/api/cart
- [01:05] 200 GET https://app.example.com/api/orders

## Appendix: Console Dump

```
[00:18] error: Checkout failed: 500 {"error":"coupon_expired","requestId":"req_8a41c2"}
[00:18] ERROR (page) TypeError: Cannot read properties of undefined (reading 'orderId')
```

Note what survived the trim. At Compact, ordinary JSON bodies became shape summaries and four repeated cart polls collapsed to one line. The marker, the note, the narration, the annotation, the error, and the request the error links to were all kept in full. That is a rule, not a heuristic: protected events are never trimmed at any level.

The same moment at every level

Levels are cumulative transform pipelines, not a budget solver, so the output is deterministic and explainable. Here is the failing request rendered four ways.

Full (L0) and Standard (L1) — real bodies

[00:17] 🌐 500 POST /api/checkout
- Request (application/json):
```json
{"cartId":"c_7731","email":"ada@example.com","couponCode":"SPRING24",
 "paymentToken":"«redacted»"}
```
- Response (application/json, full body: `network/007-api.example.com-checkout.json`):
```json
{"error":"coupon_expired","requestId":"req_8a41c2","expiredAt":"2026-07-31T00:00:00Z"}
```

Compact (L2) — shape summary

[00:17] 🌐 500 POST /api/checkout
- request shape: `{ cartId, email, couponCode, paymentToken }`
- response shape: `{ error, requestId }`

Minimal (L3) — errors keep their bodies anyway

At Minimal, network bodies are dropped — except for requests linked to an error or with a status of 400 or above. This one is both, so it stays:

[00:17] 🌐 500 POST /api/checkout
- Response (application/json):
```json
{"error":"coupon_expired","requestId":"req_8a41c2","expiredAt":"2026-07-31T00:00:00Z"}
```
Level Target Best for
Full (L0) everything You need every response body, verbatim.
Standard (L1) ~150k tokens Large-context models; bodies truncated to the first 4 KB.
Compact (L2) ~50k tokens The default for coding agents. Self-sufficient without assets.
Minimal (L3) ~15k tokens Long sessions, tight windows, or pasting into a chat box.

The prompt that goes with it

You are a debugging assistant. Below is report.md, a chronological recording of a user session in a web app, captured by a browser extension. [mm:ss] timestamps are the join key across events, screenshots, and the voice transcript. Section headings mark navigations and tab switches. A ×N similar marker means repeated identical calls were collapsed. The user's own markers and notes appear as blockquotes and are the most important signal.

Summarize what the user was trying to do; identify the first error and its likely root cause, citing the timestamps and the specific requests and console lines involved; then suggest the concrete code change. If a detail you need was trimmed, say which [mm:ss] event you want re-exported at a higher verbosity level.

Given the report above, an agent has everything it needs to say: the coupon expired, the API returned 500 without an orderId, renderConfirmation dereferences it unconditionally at checkout.tsx:214, and the success screen renders regardless of status. That is three findings from one file, none of which required the agent to guess.

What is not in the report

The card number shows as «redacted» because redaction runs at capture time, before anything is written to storage — passwords, authorization headers, token-like JSON and form fields, and sensitive URL parameters are masked on the way in, not scrubbed on the way out. You can add your own rules, or turn redaction off per session. More about the local-first design.

Record your next bug instead of describing it

Free, open source, and everything stays on your machine.