Somebody just noticed your product is broken. You have about ten seconds.
The toast disappears. The failed request is gone. The state scrolls away. Relay is one button that captures all of it at that moment, scrubs it on the server, and hands a brief to a person or a coding agent.
What actually happens on a press
The screen is photographed, without asking
The page rasterises its own DOM with every scroll pane opened, so the picture is the whole view rather than the part that fit on the glass. Asking somebody for a screenshot is a button combination, a photo library and a picker, and by then the thing they wanted to report has scrolled off.
The failing request is already in hand
A recorder installs before your own code and keeps a bounded ring buffer: requests, uncaught errors, your console errors, route changes, and what was pressed in order. You cannot capture the past, and the failing call is usually the bug.
Everything else is extracted
Alerts, the conversation as rendered, tables as rows, forms with their values, open dialogs and whether their buttons were disabled, plus a greppable DOM outline.
It is scrubbed on the server
Before the first write, because a browser can be edited and the copy on disk is the one that leaks. Keep the shape, drop the identity: a defect lives in the shape, and the shape survives dana@acme.com becoming <email 1>.
The fixer gets a brief, not a dump
Hand over JSON and somebody has to become an archaeologist before they can become an engineer. So it is a document: what they said, what they pointed at, the commit they were running, and the failing request flagged inline.
What the fixer actually receives
Not a ticket that says “it’s broken”, and not a JSON dump. A document, in the order somebody debugging reads in.
I pressed Save and the row went back to the old amount.
## The failing request
POST /api/batches/:id/settle → 500
body: { "amount": "<redacted>", "clientId": "<uuid 1>" }
## Console, which nobody was shown
TypeError: Cannot read properties of undefined (reading 'total')
at settleBatch (statements.tsx:214:9)
## What they pressed, in order
/clients → /clients/:id → /clients/:id/statements
click button[data-testid="save-statement"]
## On the placeholders
<email 1> and <uuid 1> are stable within this report and nowhere else.
The original is not recoverable: redaction runs before the first write.
Shortened. A real brief also carries the screenshot, the platform, every region of the screen as itself, and your standing instructions.
Two script tags. Any stack.
No build step, no framework, no dependency. The split matters: the recorder has to run before your own code, because a request that failed before it parsed is a request nobody will ever see.
Launches a cloud agent on the repository with the brief as its prompt and the screenshot attached.
Claude
Writes a first pass at the diagnosis onto the report: the likely cause, the evidence for it, where to look, and what would disprove it.
Linear or GitHub
Opens an issue in your team, with a link back to the report and its picture.
A webhook
Signed JSON, for whatever you already run.
Nowhere
A real configuration. Reports are still captured and still read in the console.
Forty people, one bug, one issue
Reports are grouped by what the machine observed: the same uncaught error, the same failing call. A report with no machine evidence is left ungrouped and says so, because grouping on the screen alone would hide distinct bugs behind each other.
1 issue
however many people hit one fault
1 email
not one per report, unless you ask for that
0 lost
every repeat is still captured and still stored
What it will not do
Record a request or response body. Headers and bodies carry tokens, and status and duration answer the question.
Capture a password field. It is photographed empty at source, never captured rather than redacted afterwards.
Let a client decide what was redacted. A browser can be edited; scrubbing happens on the server before the first write.
Merge a pull request because CI is green. Green proves the unit behaves, not that the feature is right.
Refuse somebody's report because their vendor is over a plan limit. The person who pressed the button is not the customer, and their evidence exists once.