[]previous and next page

Errors in production

Details of an unexpected error never reach the client in production.

For agentsUpdated

Details of an unexpected error never reach the client in production. What the client sees:

Thrown Status Message shown
HttpError 4xx its status its message (written for the caller)
HttpError 5xx its status the reason phrase; the message only with expose: true or in development
validation failure 400 Invalid input and the issues
notFound() 404 Not Found
anything else 500 Internal Server Error; the detail only in development
a request that timed out 504 Gateway Timeout

Unexpected errors and HttpErrors from 500 up go to onError(error, { phase, event }) exactly once, however many layers the error passed. The phase is request, middleware, api, rpc, loader, render, stream or head, and event.id is the id the response carries in x-request-id and, for JSON answers, in error.requestId, so a bug report that quotes the id finds the log line. Without onError the handler prints [dusk] <phase> error (request <id>, digest <digest>) and the error to console.error. 4xx errors are not reported.

The digest is the short code (8 hexadecimal characters) that ties a page to its log line. The public error of an unexpected error carries it as error.digest (a route's error export and the global error page receive it), and the log line prints the same value. It is a keyed hash of the error's root cause with a key that is random per process: the message is never in it, and it cannot be checked against a guessed message from outside the process. The client gets it through the hydration data. It is not a secret, and it changes when the process restarts. See the error UI paragraph of the Dusk routing guide.

src/routes/api/fail.tsts
import { HttpError, type RequestEvent } from "@shadow-js/dusk/server";

export const GET = (event: RequestEvent): never => {
  switch (event.url.searchParams.get("kind")) {
    case "client":
      throw new HttpError(403, "Not your document");
    case "server":
      throw new HttpError(502, "upstream answered with a stack trace");
    case "exposed":
      throw new HttpError(503, "Down for maintenance", { expose: true });
    default:
      throw new Error("password for the database is hunter2");
  }
};