CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

fix: admin health card no longer reports 'Network error' when API is reachable #4740

Merged⚡ AI-generatedXSccantynz wants to mergeclaude/elegant-knuth-73u7bymainopened Jun 13, 2026
ccantynzcommented Jun 13, 2026

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:

⚠️ Network error — is the API reachable?

…while the API Endpoint Health panel right below it showed every endpoint — including /api/admin/health itself — responding successfully in ~40ms. A directly contradictory signal that misleads the admin into thinking the backend is down.

Root cause

fetchHealth() in landing/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
}
  1. await r.json() runs before the r.ok check, so a non-JSON body (an HTML error page from a misconfigured deploy, an empty body, a proxy/gateway error) throws a SyntaxError.
  2. The bare catch then labels that — and every other failure — as "Network error", even though the server clearly responded. The per-endpoint pings prove reachability because they only inspect r.status and never parse the body, hence the contradiction.
  3. No request timeout (the pings use 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 < 500 as "reachable" is intentional (e.g. /api/grammar POST {} 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:

  • HTTP error → surface the API's error field, else HTTP <status> <statusText>.
  • Non-JSON 2xxReachable, but returned a non-JSON response (HTTP <status>).
  • Thrown fetch → genuine transport failure, with a timeout distinguished from an unreachable host.

Also adds a 6s timeout for parity with the pings.

Testing

  • landing/: tsc --noEmit clean; npm run build (tsc && vite build) succeeds (1941 modules).

Generated by Claude Code

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 Building Building Preview Jun 13, 2026 7:20am

Cross-repo impact

See what breaks downstream if this PR merges.

Analyze →
⮌ Merged

This pull request was merged into main.

c comment · e edit title · m merge · a approve · r request changes · ? shortcuts