A ticket arrives with the evidence incomplete and the reply writes itself: could you send the steps you took? That is the right question only when the missing part belongs to the reporter’s own visit. When it belongs to a setting somebody else owns, asking again returns a second ticket with the same hole in it.
The reply to a gapped ticket is a bet on who owns the gap
Two questions hide inside “why is there no recording here”, and a triage queue runs on the second one. The first is what went wrong. The second is who was in a position to make it not go wrong, and that is the one deciding where the ticket goes next.
Most tooling answers the first and leaves the second to be inferred. The inference is cheap and it lands in the same place almost every time: the reporter is the only person the ticket names, so the reporter is who gets written to. That costs a round trip, and a wrong guess returns a second ticket with the same gap in it, filed by somebody who now believes their bug report was the problem.
Whether a recorder should write down why it produced nothing is a question about building one, and the hard part of session replay is where that argument lives. The question after it is the one a queue meets every day: the explanation is on the ticket, and somebody has to decide what to do with it.
Sort the gaps by owner before sorting them by cause
The useful sort is by who could have changed the outcome rather than by what the tool failed to capture, and there are three owners, plus the state most queues are actually in.
| The gap belongs to | What the explanation names | A second report | Who the ticket goes to |
|---|---|---|---|
| One visit | The browser, the connection, or what the page was doing while it loaded | May come back carrying the evidence | The reporter, asked something narrower than “please send steps” |
| The project | A setting on the project itself | Arrives with the same gap | An admin on the project, before anybody writes back |
| The organization | The plan the project is billed under | Arrives with the same gap | Whoever signs for the subscription, which is nobody in triage |
| Nothing you can read | No explanation at all, only an empty player | Unknowable in advance | The reporter, by default, and that default is where the round trip comes from |
The last row is worth recognising, because it is where the reflex comes from rather than a lapse of judgement. Writing to the reporter is the only move available when a gap is unattributed, so a queue meeting unattributed gaps every day learns to ask first and read afterwards.
Which makes the demand a triager should put on their own tooling a narrow one: an explanation that names an owner, rather than a longer catalogue of causes. Ours does it by making the owner readable from the sentence, and the ordering is the order the code decides in rather than a label attached afterwards.
Only the visit is worth a second run
Bug triage treats a gap belonging to one visit as recoverable because it genuinely is. The visitor’s browser was missing an API the recorder needs, which arrives as unsupported_capability. The connection could not carry the upload, which is network_policy_ineligible. Recording stopped earlier to protect the page and cannot restart until the visitor reloads, which arrives as hard_self_breach. Each of those is a fact about a single page load, and the next one is different.
That is also the class where steps to reproduce still earn their keep, because the reporter is the only person who was there. What changes is the question. “Please send steps” invites a paragraph the reporter already wrote once, while “which browser, and does it happen on your phone as well” is answerable in a line and is what the explanation was already pointing at.
One reason in this class is coarser than a reader would like, and knowing that before a round trip is worth more than discovering it after one. startup_protection covers a page that had not yet qualified to start recording, and it covers a tab that went to the background or a host that paused the widget, because all three reach the recorder as the same lifecycle state. Read it as the recorder never having reached the state where it records, which leaves the visit itself worth asking about rather than settled.
A switch and a contract are different escalations
The two gaps nobody in triage can close read almost identically on the ticket. “Session replay is turned off for this project.” and “This project’s plan does not include session replay.” arrive in the same place and send a reader to two different people. One is a checkbox an admin owns. The other is a line item somebody signs for, and answering it leaves the engineering side of the building entirely.
The plan one is the rarer of the two in practice, because both published plans include session replay. The gate stays enforced anyway: capability is held per organization rather than fixed in a build, so an operator can move a tier without a release and the widget has to be told the truth either way.
What matters more than the split is which one gets named when both are true, because that is where an explanation sends somebody to the wrong person. An organization that turned recording off does not get replay back by paying, so the plan is not the operative fact even where the plan would have refused as well. The explanation names the project’s own switch, and the recorder makes the same choice from the other end: it reads the project’s recording switch before it looks at anything about the page it is running on. The rule is portable to any tool that reports its own gaps. When two owners are both true, name the one the reader can act on from the access they already have.
What the explanation will not do for you
Three limits sit behind that sort, and the first belongs to us rather than to the practice.
The owner is legible in the sentence and it is not a field on a screen. Nothing groups a queue by who can act, nothing filters a board on it, and a person or an agent reading session replay evidence gets the same fields either way, which is the parity rule doing its job. The sorting is the reader’s own, done one ticket at a time.
The second is a property worth demanding rather than a limit to plan around. A code this build has no sentence for is shown raw, with the code in parentheses after the word declined, and marked unrecognised. It is never flattened into no reason at all, because a panel deployed behind the server is the normal case and falling silent on the unknown would break the one thing the explanations exist to do. The code is stable even where the wording has not caught up, so it is safe to quote in a ticket and escalate.
The third is a trap in the vocabulary rather than in the tooling. A list of gaps that admits things which are not gaps routes tickets to people with nothing to do. When a second recorder finds the document already claimed, the page is being recorded, by the recorder that got there first, so nothing is missing and nothing is filed. A privacy configuration the page could not supply is the mirror image: capture ran with less than the project asked for and fell back to baseline masking, which is worth reporting on the row it affected and is never the answer to why there is no recording here.
That is a smaller promise than a taxonomy on a screen, and it is the one worth having first. What a triage queue runs on is each reason naming somebody, so that the reply to a reporter goes out only when a reporter is the person who can answer it.