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

> ## 📌 MARKER [00:19]: this is the bug
### [00:24] Annotation
- **arrow** #ff5a4d on div.summary__total ("Total $42.00")
- **text** #ff5a4d "still says success"

> 🎙️ [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 similarmarker 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.