fix: admin health card no longer reports 'Network error' when API is reachable #4740
ccantynzcommented Jun 13, 2026
Originally written by @vercel[bot] on GitHub.
The latest updates on your projects. Learn more about Vercel for GitHub.
| Project | Deployment | Actions | Updated (UTC) |
|---|---|---|---|
| voxlen | Preview | Jun 13, 2026 7:20am |
Cross-repo impact
See what breaks downstream if this PR merges.
⮌ Merged
This pull request was merged into main.
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts
Originally written by @ccantynz-alt on GitHub.
Imported from https://github.com/ccantynz-alt/voxlen/pull/61
Problem
In the admin dashboard's Diagnostics tab, the Environment Variables health card displayed:
…while the API Endpoint Health panel right below it showed every endpoint — including
/api/admin/healthitself — responding successfully in ~40ms. A directly contradictory signal that misleads the admin into thinking the backend is down.Root cause
fetchHealth()inlanding/src/components/Dashboard.tsx:const r = await fetch(`${VOXLEN_BASE}/api/admin/health`, { headers: { Authorization: ... } }); const json = (await r.json()) as HealthData & { error?: string }; // throws if body isn't JSON if (!r.ok) { setHealthError(json.error ?? "Failed"); } else { setHealth(json); } } catch { setHealthError("Network error — is the API reachable?"); // swallows parse errors too }await r.json()runs before ther.okcheck, so a non-JSON body (an HTML error page from a misconfigured deploy, an empty body, a proxy/gateway error) throws aSyntaxError.catchthen labels that — and every other failure — as "Network error", even though the server clearly responded. The per-endpoint pings prove reachability because they only inspectr.statusand never parse the body, hence the contradiction.AbortSignal.timeout(6000); this didn't), so a hung request spins the spinner indefinitely.The per-endpoint pings are correct and left unchanged — treating any
status < 500as "reachable" is intentional (e.g./api/grammarPOST{}legitimately returns 400; that still proves the endpoint is up and routing).Fix
Read the body once as text, parse defensively, and report the actual condition:
errorfield, elseHTTP <status> <statusText>.Reachable, but returned a non-JSON response (HTTP <status>).Also adds a 6s timeout for parity with the pings.
Testing
landing/:tsc --noEmitclean;npm run build(tsc && vite build) succeeds (1941 modules).Generated by Claude Code