Session Recorder → Bug reports AI coding agents can fix
Guide
Browser bug reports for AI coding agents: what Claude Code, Cursor and Codex actually need
Coding agents are good at reading code and blind to runtime. Almost every frustrating agent debugging session comes down to that one asymmetry — and to a bug report that did nothing to fix it.
Contents
- The asymmetry at the heart of agent debugging
- Why a screenshot is the weakest evidence you can send
- The eight signals a browser bug report needs
- Temporal ordering is the diagnosis
- The context window problem: more is not better
- Deterministic trimming beats AI summarization
- Markdown is the interchange format
- Redaction: what must never be in the file
- Recorded evidence vs. driving a live browser
- Local capture vs. cloud capture
- A checklist you can steal
- What this looks like in practice
- Frequently asked questions
The asymmetry at the heart of agent debugging
An AI coding agent has an unusually complete view of one half of your system and no view at all of the other. It can read every file in the repository, follow imports across packages, and hold more of the codebase in working memory than you can. It cannot see a single thing that happened in the browser.
So when you type “the checkout page is broken,” the agent does the only thing available to it: it generates hypotheses from the code. It reads the checkout module, notices four things that could plausibly break, and picks one. Sometimes it picks right. When it picks wrong, it writes a confident patch to code that was never involved, and you spend the next twenty minutes discovering that.
This is not a reasoning failure. It is an evidence failure. The agent is doing inference because you gave it nothing to observe. Every technique in this article is a way of converting inference back into observation.
The practical target is simple to state: an agent should never have to guess what the network did, what the console said, or what you clicked. Anything else it can work out from the code.
Why a screenshot is the weakest evidence you can send
Screenshots feel like strong evidence because they are strong evidence for a human. A colleague looking at a screenshot brings a model of your app, your recent work, and the class of bugs you have been chasing. They do not need the before and after because they can reconstruct it.
An agent cannot. A screenshot of a broken screen contains:
- one frame, with no preceding state and no following state;
- no indication of which action produced it;
- no network activity whatsoever;
- no console output;
- no timing;
- and no statement of what the correct frame would have looked like.
For a visual bug — misaligned layout, wrong colour, overlapping text — a screenshot is close to sufficient, because the symptom is the pixels. For anything involving data, state, or asynchrony, it is the least informative artifact you could have chosen. The agent's most reasonable response to a screenshot of a failed checkout is a question, and if it does not ask one, it is guessing.
This does not mean stop sending screenshots. It means stop sending only screenshots. A screenshot annotated with an arrow — “this number is wrong” — is genuinely valuable, because it resolves the one thing text is bad at: which of the fourteen things on screen you meant. It is corroboration. It is not the case.
The eight signals a browser bug report needs
Over a lot of agent debugging sessions, the same eight pieces of evidence turn out to do nearly all the work. They are listed roughly in order of how often they contain the answer.
1. The network exchange — both halves of it
For web application bugs, the request and its response are the single most valuable artifact, and the response body matters more than the request. A great many “frontend bugs” are the frontend faithfully rendering something the API should not have sent: a null where an object was promised, an empty array, a 200 with an error payload inside it, a field renamed in a deploy nobody told you about.
A report that lists POST /api/checkout — 500 is far better
than nothing and still leaves the agent guessing. A report that includes
{"error":"coupon_expired","requestId":"req_8a41c2"} ends the
investigation. Capture method, URL, status, timing, request body, and
response body — and note when a body was truncated, so the agent knows
the difference between “empty” and “omitted.”
2. Console output and exceptions, with stacks
The console is where the browser tells you which line gave up. A stack trace maps a runtime symptom directly onto a file and line number in the repository the agent is already reading, which makes it the cheapest possible bridge between the two halves of the system.
Two refinements matter. First, deduplicate: a React render loop can emit the same warning eight hundred times, and eight hundred copies is not eight hundred times the information — one copy with a count is. Second, link exceptions to the request that caused them. “TypeError reading 'orderId'” and “POST /api/checkout returned 500” are the same finding stated twice, but only if something in the report says so.
3. The sequence of interactions
Which element you clicked, what you typed, what you selected, in what
order. This is what turns a report into a reproduction. It also
disambiguates the cases that make bug reports useless: you clicked
Submit twice; you typed into the field before the form finished
hydrating; you hit Enter rather than clicking the button, and only the
button handler calls preventDefault.
Record the element, not just the coordinates: text or accessible label
plus a selector. CLICK "Place order" (button.primary) is
something an agent can grep for. click at (412, 908) is not.
4. The user's intent
The most underrated signal, and the only one an agent cannot possibly derive: what you expected instead. “The total should be forty-two dollars” converts an ambiguous screen into a testable assertion. It also prevents the failure mode where the agent finds a bug — a real one, in code that had nothing to do with your complaint — and fixes that instead.
Say it out loud, write it as a note, or drop a marker at the moment things go wrong. However it gets in, it should be the one thing your pipeline never drops.
5. Timing
Not durations for their own sake, but relative ordering with enough resolution to establish causality. “The exception fired 200 ms after the request came back” and “the exception fired 4 seconds before it” describe completely different bugs. Race conditions, double submissions, stale closures, and effects firing on the wrong render are all invisible without a clock.
6. Page and route context
The URL at the moment of failure, including SPA route changes that never touch the server. Navigation events are also the natural section breaks of a session: they turn a flat log into chapters, and they tell the agent which component tree was mounted when the error fired.
7. The visual state, pointed at
A screenshot at the moment of failure — ideally annotated. Not as the primary evidence, but because some symptoms genuinely are visual, and because an arrow resolves “which thing is wrong” in a way that three paragraphs of prose does not.
8. Environment
Browser and version, viewport size, and anything else that varies between your machine and CI. Cheap to include, occasionally the entire answer.
Temporal ordering is the diagnosis
If you take one idea from this article, take this one. Most web bugs are not “this function is wrong.” They are “these things happened in this order.” The bug lives in the relationship between events, which means a report that captures every event perfectly but scrambles their order has destroyed the finding.
This is exactly what happens when a bug report is assembled by hand from separate sources. You paste a stack trace from the console, then a request from the network tab, then a screenshot from earlier, then a description written afterwards. Each piece is accurate. The causal chain connecting them exists only in your head, and you have not written it down — usually because by the time you are writing the report, you have already forgotten the two seconds that mattered.
Everything in the report should therefore share one clock. A single
monotonic timeline, with every event stamped relative to the start of the
session, is what lets an agent say “the 500 at 00:17 caused the exception
at 00:18, which is why the screenshot at 00:24 shows a success state that
was never true.” It also gives the agent a join key: [00:17]
in the narrative, in the network index, in the screenshot filename, and
in the transcript all refer to the same moment.
Ask the agent to cite those timestamps in its answer. It makes the reasoning checkable against the report, which is the cheapest hallucination defence available in this workflow.
The context window problem: more is not better
Having established that you should capture everything, here is the complication: you should not send everything.
A three-minute session against a modern web app produces an enormous amount of data. Analytics beacons every few seconds. Font and image requests. Polling endpoints. A single JSON response can run to hundreds of kilobytes. Serialize all of it and a short recording becomes hundreds of thousands of tokens, most of it noise.
Large context windows do not solve this. They make it possible to send the noise, which is not the same as the model attending to it. Recall degrades in the middle of long inputs, the signal you cared about competes with eight hundred identical polling requests, and — if you are using a coding agent — every token spent on a font request is a token not spent on your codebase.
The economics are unforgiving in the other direction too:
- too much, and the agent misses the finding in the noise;
- too little, and it asks a question you then have to answer manually, or worse, guesses;
- and you do not get to know which happened until after the fact.
The way out is to make size an explicit, adjustable control rather than an accident of how long you recorded — and to know the number before you send it, not after the model complains.
What to trim, in order
- Static assets and analytics. Images, fonts, CSS, and telemetry hosts. Collapse them to a single summary line. They are almost never the bug.
-
Repeated identical requests. Forty polls of
GET /api/statusthat all returned 200 are one line plus a count. Critically: a request whose status differs from its neighbours must survive in full. Anomalies are the entire point. -
Body volume, not body existence. Truncate long bodies with a
note of the original size, or reduce JSON to a shape summary —
{ users: Array(50) of { id, name, email } }— which is often all the agent needed. The shape answers “is the field there and is it the right type,” which is most schema bugs. - Screenshot frequency. Keep the moments that matter: errors, annotations, state transitions. Drop near-duplicate frames.
- Low-information interactions. Coalesce runs of scrolls. Keep clicks and typing.
What to never trim
- the user's markers, notes, and narration — their intent is irreplaceable;
- errors and exceptions, at any volume;
- the request an error links to, including its body;
- annotations and the screenshots they mark up;
- the ordering of what remains.
These are protections, not preferences. If a level ever drops one of them, the level is wrong.
Deterministic trimming beats AI summarization
The obvious way to shrink a bug report is to ask a model to summarize it. This is a mistake, for four reasons.
It is lossy in exactly the wrong place. A summarizer keeps what
looks important. Bugs hide in what looks unimportant: the one request out
of forty that returned 304, the field that was null instead
of 0, the two events that arrived in the wrong order.
It is not reproducible. Run it twice, get two different reports. Now “the agent said X” is not a claim anyone can check, because the input it saw no longer exists.
It launders hallucination into evidence. A summarizer that misreads a status code produces a bug report that states the wrong status code as fact. The downstream agent has no way to tell that line from an observed one. You have converted a model's guess into something that looks like data.
It costs a round trip and, usually, an upload. To summarize your session, someone has to send your session somewhere.
Deterministic trimming — fixed rules, applied in a fixed order — has
none of these problems. The same recording at the same level produces the
same report, byte for byte. Every line in it was observed. And because
the rules are stated in advance, the agent can be told what they are:
“×N similar means repeated identical calls were collapsed”
is a fact it can reason about, unlike “this section was summarized.”
Keep the full recording locally and treat trimming as a rendering step. Then re-exporting at a different level is free, and “I need the full body from 00:17” costs you a click rather than a second reproduction.
Markdown is the interchange format
Not because it is fashionable, but because of what the alternatives cost.
Raw JSON of an event stream is precise and expensive: braces, quotes, and repeated key names consume a large fraction of the tokens, and deeply nested structures are harder for a model to narrate over than prose. Keep JSON as a secondary artifact for programmatic use, not as the thing you hand the agent first.
HAR files are the standard network export and a poor bug report: enormous, base64-heavy, and network-only. They contain no interactions, no console, no intent. An agent handed a HAR spends most of its attention parsing it.
Video is dense for humans and mostly opaque to text models. Useful as an attachment, useless as the primary artifact.
Markdown wins because it is nearly free to tokenize, it survives being pasted into anything, headings give the model structure to navigate, fenced code blocks carry bodies verbatim, and image references let an agent that can open files go look at the screenshot. It is also readable by you, which matters more than it sounds: you should be able to check what you are about to hand over.
Some format rules that pay for themselves:
- put the timestamp first on every line, so the clock is scannable;
- use headings for navigations, so the session has chapters;
- make the user's own words visually distinct — blockquotes work well;
- fence every captured body, and label the language;
- state trimming decisions inline (
truncated from 812 KB), so absence is never ambiguous; - end with an index of every network request and a console dump, so the agent can scan for anomalies without re-reading the narrative.
Here is a complete example applying all of that.
Redaction: what must never be in the file
A faithful recording of a real session is, by construction, a security problem. It contains whatever your app sent and received: session cookies, bearer tokens, API keys in headers, personal data in response bodies, and whatever was on screen. The moment you hand that to a model — or commit it, or attach it to a ticket — it has left your control.
The rule that matters is redact at capture time, not at export time. A tool that scrubs on the way out has already persisted the secret to disk. A tool that scrubs on the way in never had it. The difference shows up the first time a laptop is lost or a temp directory is synced somewhere.
What to mask by default:
Authorization,Cookie, andSet-Cookieheaders;- token-like JSON and form fields —
token,secret,password,apiKey, and their variants; - sensitive URL query parameters;
- password and payment inputs, at the DOM level, before the value is ever recorded.
Then read the report before you send it. Automated redaction handles credentials, which have recognizable shapes. It cannot know that the customer list your admin panel returned is not yours to share. That judgement is yours, and it takes thirty seconds.
Recorded evidence vs. driving a live browser
Agents can increasingly drive a browser themselves, through an MCP server or a browser extension. It is a genuinely good capability and it does not replace recorded evidence — the two answer different questions.
| Use a live browser when… | Use a recording when… |
|---|---|
| The agent needs to explore — try inputs, bisect, poke at state. | The repro needs your session, your data, or your permissions. |
| The bug is trivially reachable from a public URL. | It takes six steps in a specific order to reach. |
| You are verifying that a fix worked. | It is timing-dependent and does not reproduce on demand. |
| The state is cheap to recreate. | Reproducing it costs real money, real email, or real data. |
| The agent needs to see the result of its own change. | “Wrong” requires human judgement about what should have happened. |
There is also a cost asymmetry worth noting. Driving a browser spends agent turns: navigate, screenshot, read, click, screenshot, read. Each step consumes context and time, and the agent has to find the bug before it can start fixing it. A recording front-loads all of that into one file you produced in ninety seconds while doing something you were going to do anyway — reproducing the bug.
The natural combination: record the failure, hand over the report, let the agent fix it, then let it verify the fix in a live browser.
Local capture vs. cloud capture
Most bug-capture tools are cloud products, and for their intended job — moving a bug from a non-technical reporter to an engineering team — that is the right architecture. Links, tickets, comments, and integrations all need a server.
The agent workflow is different. You are the reporter, you are the engineer, the fix happens on your machine, and the recipient is a process running in your terminal. A cloud round trip in the middle of that loop adds an account, a network dependency, a retention policy, and a compliance question, in exchange for nothing the loop needed.
Local capture matters most when:
- the app is regulated, internal, or handling real customer data;
- the feature is unreleased and a recording is a leak;
- your organization has a policy about third-party processors that a browser extension quietly violates;
- or you simply do not want a permanent archive of your screen on someone else's infrastructure.
The trade-off is real: no shareable links, no ticket integrations, no cross-device history. If bugs arrive from other people, take the cloud tool. If bugs arrive from you and leave to an agent, the file is enough. More on that design.
A checklist you can steal
Before handing a browser bug to an agent, the report should contain:
- ☐ the ordered sequence of interactions, with element labels and selectors
- ☐ every non-static network request: method, URL, status, timing
- ☐ response bodies for anything that failed, or a shape summary if it succeeded
- ☐ console output and exceptions, deduplicated, with stacks
- ☐ an explicit link from each error to the request that caused it
- ☐ the URL and route at the moment of failure
- ☐ a screenshot of the failing state, annotated if the symptom is visual
- ☐ one sentence of what you expected instead
- ☐ a single clock joining all of the above
- ☐ credentials masked, and a human skim before it leaves the machine
- ☐ a size you checked, not a size you hoped for
Assembling that by hand takes ten to fifteen minutes per bug and you will get it wrong under deadline pressure, which is precisely when you need it most. That is the argument for recording it instead.
What this looks like in practice
Session Recorder is a free, open-source, local-first Chrome extension for recording web bugs for AI coding agents — an implementation of everything above. It was built because doing this by hand, repeatedly, is exactly the kind of work that should not be manual.
-
One clock. Clicks, typing, text selections, navigations, network
requests and responses, console output, exceptions, screenshots, tab
video, voice narration, annotations, and uploaded files all land on a
single
[mm:ss]timeline in the order they happened. - Real network capture. Request and response bodies through the Chrome DevTools Protocol, with errors linked to the request that caused them.
- Your intent, recorded. Talk while you record; narration is transcribed and anchored to what you were doing when you said it. Drop markers, write notes, draw an arrow at the broken thing.
- Token-budgeted export. Four levels — Full, Standard, Compact, Minimal — each with a live token estimate, so you pick a size instead of discovering one. Re-export the same recording at any level, losslessly.
- Deterministic trimming. Cumulative transform pipelines, no model call. Markers, notes, narration, annotations, and every error are protected at every level, by rule.
- Redaction at capture time. Passwords, authorization headers, and token-like fields are masked before anything is written to storage.
- Local-first. No account, no backend, no telemetry. The output is a zip in your Downloads folder.
Workflow guides for Claude Code, Cursor, Codex, and ChatGPT, or read a complete example report first.
Frequently asked questions
What is an AI-ready bug report?
An AI-ready bug report is a single, self-contained artifact that gives a coding agent the runtime evidence it cannot observe itself: the ordered sequence of user interactions, the network requests and their response bodies, console output and exceptions, page context, screenshots of the failing state, and an explicit statement of what the user expected. It is sized to fit the model's context window and formatted as plain text — usually Markdown — so the agent can read it as a file.
Why are screenshots not enough for an AI coding agent?
A screenshot shows one frame with no before and no after. It cannot show the request that failed, the status code that came back, the exception the browser threw, or the order in which those happened. For web bugs the ordering is usually the diagnosis, and a screenshot has no ordering at all. Screenshots are useful as corroboration of the visual symptom, not as the primary evidence.
What context do Claude Code, Cursor and Codex actually need to fix a browser bug?
They already have the source code. What they lack is runtime state: which endpoint was called with which payload, what came back, what the console said, what the user clicked immediately before, and what the user expected to happen. Supply those and the agent's job becomes reading code against evidence rather than guessing at plausible causes.
Should I give an agent a live browser or a recording?
Give it a live browser when the agent needs to explore — trying inputs, verifying a fix, walking a flow it can trigger itself. Give it a recording when the bug is yours to reproduce: it needs your login, your data, a specific sequence, a timing-dependent race, or a judgement call about what 'wrong' looks like. The two compose: record the failure, then let the agent verify the fix live.
How large should a bug report be?
Small enough that the model can attend to all of it, which in practice means well under the nominal context limit. A report of roughly 50k tokens is a good default for a coding agent that is also holding your codebase; drop to about 15k for a chat window or a long session. Prefer trimming bodies and collapsing repeated requests over dropping events, because ordering is the signal you cannot reconstruct later.
Is it safe to send a browser recording to an AI service?
Only if you know what is in it. A recording of a real session can contain session tokens, authorization headers, personal data in response bodies, and whatever was on screen. Redact credentials at capture time rather than at export time, read the report before you upload it, and prefer a local-first tool if the data cannot leave your machine at all.
Record your next bug instead of describing it
Free, open source, and everything stays on your machine.