fix(reports): add error handler and improve error messages for diagnosis #51

Merged
ghost merged 2 commits from fix/reports-error-handling into main 2026-03-18 13:36:32 +00:00
ghost commented 2026-03-18 13:29:11 +00:00 (Migrated from github.com)

Root cause

/api/reports/clients was crashing with a 500 on every request. In the churn-risk query, a raw JavaScript Date object was embedded in a sql template literal inside .having():

// before — postgres-js can't serialize a Date in a sql template:
.having(sql`MAX(...) < ${ninetyDaysAgo} OR MAX(...) IS NULL`)

postgres-js does not know how to parametrize a raw Date in this position; it throws a type error, the route handler crashes, and Hono returns an empty 500. The frontend then shows "Failed to load report data" with no further detail.

Fix (two commits)

87b038d — actual bug fix (CEO)

  • Convert the Date to an ISO 8601 string and add an explicit ::timestamptz cast so PostgreSQL handles the comparison correctly:
const ninetyDaysAgoISO = ninetyDaysAgo.toISOString();
.having(sql`MAX(...) < ${ninetyDaysAgoISO}::timestamptz OR MAX(...) IS NULL`)

5e185f0 — error surfacing (CTO)

  • Add reportsRouter.onError() so any future unhandled exception returns a JSON { error, message } 500 and logs to stdout, instead of an empty response.
  • Improve the frontend error: shows which specific endpoint failed + HTTP status + first 120 chars of response body.

Test plan

  • Navigate to Reports page — loads without error
  • Churn risk section populates correctly
  • If any report endpoint fails in future, the UI error identifies the specific endpoint and message

Fixes #49

🤖 Generated with Claude Code

## Root cause `/api/reports/clients` was crashing with a 500 on every request. In the churn-risk query, a raw JavaScript `Date` object was embedded in a `sql` template literal inside `.having()`: ```typescript // before — postgres-js can't serialize a Date in a sql template: .having(sql`MAX(...) < ${ninetyDaysAgo} OR MAX(...) IS NULL`) ``` `postgres-js` does not know how to parametrize a raw `Date` in this position; it throws a type error, the route handler crashes, and Hono returns an empty 500. The frontend then shows "Failed to load report data" with no further detail. ## Fix (two commits) **`87b038d` — actual bug fix (CEO)** - Convert the `Date` to an ISO 8601 string and add an explicit `::timestamptz` cast so PostgreSQL handles the comparison correctly: ```typescript const ninetyDaysAgoISO = ninetyDaysAgo.toISOString(); .having(sql`MAX(...) < ${ninetyDaysAgoISO}::timestamptz OR MAX(...) IS NULL`) ``` **`5e185f0` — error surfacing (CTO)** - Add `reportsRouter.onError()` so any future unhandled exception returns a JSON `{ error, message }` 500 and logs to stdout, instead of an empty response. - Improve the frontend error: shows which specific endpoint failed + HTTP status + first 120 chars of response body. ## Test plan - [ ] Navigate to Reports page — loads without error - [ ] Churn risk section populates correctly - [ ] If any report endpoint fails in future, the UI error identifies the specific endpoint and message Fixes #49 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This repo is archived. You cannot comment on pull requests.