Files
lefarcen f23c96edff fix(web): classify deploy failures instead of the generic "Error" code (#5554)
* fix(web): classify deploy failures instead of the generic "Error" code

`artifact_deploy_result` reported `err.name` on the deploy catch path,
which collapses to the generic "Error" for a plain thrown Error — ~half
of failed deploys (~40/day) land in that opaque bucket with no way to
tell an auth failure from a network drop from a provider 5xx, so the
deploy failure rate can't be root-caused from telemetry.

Add `deployErrorCode(err)` (mirrors export-error-code.ts / #5220):
structured `.code` wins, then map the common transport / provider-API
signatures (NETWORK, TIMEOUT, RATE_LIMITED, FORBIDDEN, NOT_FOUND,
HTTP_<status>, desktop sidecar skew) to fixed codes, else fall back to
err.name. Only fixed codes are emitted — never free-form message text —
so no deploy token, account id, zone, or URL can leak into analytics.

* fix(web): preserve deploy HTTP status/code so classification isn't lost upstream

Review follow-up (#5554): deployErrorCode can only emit HTTP_<status> or a
structured code if that survives into the deploy catch, but deployProjectFile
threw a bare `new Error(payload.error.message || ... )`, dropping the status
whenever the provider/daemon returned a human message. So the daemon-backed
deploy failures the classifier was meant to bucket still collapsed to "Error".

deployProjectFile now attaches a `.code` to the thrown error: a structured
provider/daemon `error.code` when present, else `HTTP_<status>`. deployErrorCode
reads `.code` first, so those failures now bucket by status instead of "Error".
Transport failures (fetch throws before a response) still classify via message.

* fix(web): ignore the daemon's generic deploy envelope code so status bucketing runs

Follow-up to @nettee's review: the daemon deploy route
(apps/daemon/src/routes/deploy.ts) collapses every non-404 provider failure's
code to `BAD_REQUEST` (and 404 to `FILE_NOT_FOUND`) while keeping the REAL
provider HTTP status on the response and the real message in the body. Since the
prior change preferred `payload.error.code`, a live 403/429/5xx was rethrown as
`{ code: 'BAD_REQUEST' }` and deployErrorCode returned that generic code, never
reaching HTTP_403 / RATE_LIMITED / etc.

Ignore the generic envelope codes (shared `GENERIC_DEPLOY_ENVELOPE_CODES`) at
both the throw site (fall back to the real `HTTP_${resp.status}`) and inside
deployErrorCode as a safety net, so status/message-based bucketing runs. Added
registry tests that exercise the actual daemon envelope (BAD_REQUEST+403 →
HTTP_403, FILE_NOT_FOUND+404 → HTTP_404) and a deployErrorCode generic-code test.
2026-07-13 13:35:56 +00:00

3.9 KiB