When a route on your juudd site fails in front of a visitor, your assistant can call site_errors and get an answer: which route, in which hour, how many times, and whether the code threw or the runtime stopped it. What it cannot get is the function's own words, and that is on purpose. The tool records what the platform saw. It does not read what your code printed.
That is the decision. The rest of this post is the problem it solves, the two outcomes the record can tell apart, and the things it refuses to keep.
The assistant used to guess
A customer writes in: "checkout stopped working." Before this tool, the assistant's next move was read_site and a guess. Nothing on the tool surface could say which route failed, when, or whether the code had thrown or been stopped.
The platform's front door saw every one of those failures go past. It told the visitor, in a sentence chosen with care, and told nobody else. So the customer's loop stalled at exactly the moment they needed it to move.
Secrets made it sharper. A site can read a secret that was never given a value, because deploy_site works before the value is set. The route that reads it then throws in front of visitors, and nobody on our side could see that.
Two outcomes, and no more
When a customer's function throws or is killed, the runtime does not report a reason. It answers with a 500 or a 503, no content type, no length, and no body. That is all the front door has to work with, so the record holds exactly two outcomes:
| seen | what it means |
|---|---|
| 503 | the request was stopped at a resource limit; the runtime does not say which one |
| 500 | the code threw, or it made too many outbound requests; from here those are one fact |
The record does not pretend to know more than the runtime said. A guess dressed as a diagnosis would send an assistant to fix the wrong thing. So the result states both meanings and leaves the telling-apart to the person who can read the code.
What it refuses to keep
A record about failures is one step from a record about visitors, and the line is drawn in the data rather than in a promise.
- No query string.
/api/reset?token=...is a visitor's secret in a visitor's URL. The path is cut before it is sent, and the table throws if a path arrives still carrying a?or a#, so a front door that stopped cutting it would be seen at once. - No headers, no address, no body, nothing the function wrote. The columns are the whole of what is kept.
- No row per request. A broken route with traffic fails thousands of times a minute, and the nine-hundredth failure says nothing the third did not. One row per site, method, path, outcome and hour carries a count, plus when inside that hour it first and last happened.
- A cap on distinct paths. An hour keeps fifty paths by name. A route with an id in it, like
/order/123, would otherwise write a row per order, so past the fiftieth a new path lands in a row called(other). - Seven days, then gone. Every write also trims what is past the window, so the thing that grows the record is the thing that shortens it. The event record is kept deliberately for months; this one is meant to disappear, and the two are not stored together.
The report goes out after the visitor has been answered, never on the way to answering them. If the control plane is down, the record is lost and the site is unaffected.
An empty answer says why it is empty
An empty result has four possible causes: the site is fine, nothing is reporting to this deployment, the failure aged out, or a report was dropped. An assistant that reads "no errors" as "healthy" would tell a customer their site is fine while checkout is broken.
So the empty answer does not say "no errors". It says nothing is recorded for the last seven days, gives the date of the oldest failure held for any site, says the record is best-effort, and says it is not proof that a site is healthy. The one fact the tool can read, how far back the record reaches, goes in the sentence.
The same care applies to discovery. A client that took its tool list before this tool existed does not know to ask. So read_site adds one line when, and only when, the site has failed in the window, and points at site_errors. A healthy site's listing is unchanged.
What is not decided
- Exception text. Getting it needs a separate decision about reading what a function printed, and this is not that decision.
- Production has never captured a real failure. The capture was proven against a site written to crash, on development. The first production proof will be a customer's route failing and the tool naming it.
- Batching. Every failure sends one report, which is accepted for now. If that is ever the thing that matters, the fix is to batch at the front door, and nothing in the record changes.
The rule, once
A failure record holds what the platform saw, per route and per hour, for seven days, and nothing a visitor or a function said. When it has nothing to show, it says how far back it looks. The tools and their limits are listed in the docs.