AlexsJones/llmfit · error · Error
Server returned an invalid JSON response.
Error message
Server returned an invalid JSON response.
What it means
Thrown by parseJsonOrThrow() in the llmfit web dashboard client when response.json() rejects, i.e. the server answered but the body is not parseable JSON. Every /api/v1/* helper (fetchSystemInfo, fetchModels, fetchRuntimes, fetchInstalled, startDownload, fetchDownloadStatus, fetchPlanEstimate) funnels through this function, so the error really means 'the HTTP layer reached something that does not speak JSON' (an HTML error page, an empty body, or a proxy response). Note the original err is discarded, so the underlying parse cause is hidden.
Source
Thrown at llmfit-web/src/api.js:110
const maxContext = trimOrEmpty(String(filters.maxContext || ''));
if (maxContext) {
const parsed = Number.parseInt(maxContext, 10);
if (Number.isFinite(parsed) && parsed > 0) {
params.set('max_context', String(parsed));
}
}
appendSimulationParams(params, simulation);
return params.toString();
}
async function parseJsonOrThrow(response) {
let payload;
try {
payload = await response.json();
} catch (err) {
throw new Error('Server returned an invalid JSON response.');
}
if (!response.ok) {
const message = payload?.error || `Request failed with status ${response.status}.`;
throw new Error(message);
}
return payload;
}
export async function fetchSystemInfo(simulation = {}, signal) {
const query = appendSimulationParams(new URLSearchParams(), simulation).toString();
const path = query ? `/api/v1/system?${query}` : '/api/v1/system';
const response = await fetch(path, { signal });
return parseJsonOrThrow(response);
}
export async function fetchModels(filters, simulation = {}, signal) {View on GitHub (pinned to a9ac7ed91c)
Solutions
- Verify the llmfit server (llmfit serve / the Axum process) is running and confirm the port the dashboard targets matches it.
- Reproduce with `curl -i http://<host>:<port>/api/v1/system` and inspect the raw body and Content-Type to see what actually answered.
- If using the Vite dev server, configure its proxy so /api/v1 forwards to the backend port (or load the dashboard directly from the llmfit server).
- If a reverse proxy sits in front, make sure it passes JSON responses through and does not substitute HTML error pages.
- Optionally improve parseJsonOrThrow to append `: ${err.message}` and the response status to the thrown error so future hits are self-diagnosing.
Example fix
// before
async function parseJsonOrThrow(response) {
let payload;
try {
payload = await response.json();
} catch (err) {
throw new Error('Server returned an invalid JSON response.');
}
// ...
}
// after - keep the cause and the status for diagnosis
async function parseJsonOrThrow(response) {
let payload;
const raw = await response.text();
try {
payload = JSON.parse(raw);
} catch (err) {
throw new Error(
`Server returned an invalid JSON response (status ${response.status}, content-type ${response.headers.get('content-type')}).`
);
}
// ...
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: confirm the API is actually serving JSON before relying on it
async function apiIsHealthy(baseUrl = '') {
const probe = await fetch(`${baseUrl}/api/v1/system`);
const type = probe.headers.get('content-type') || '';
return probe.ok && type.includes('application/json');
} Try / catch
try {
const info = await fetchSystemInfo(simulation, signal);
} catch (err) {
if (err.message === 'Server returned an invalid JSON response.') {
// transport/proxy problem, not a llmfit error - check server and proxy
showFatalError('Backend did not return JSON. Is the llmfit server running?');
}
throw err;
} Prevention
- Run the dashboard from the llmfit server itself so /api/v1/* and the assets share one origin.
- If using the Vite dev server, configure the /api proxy to the backend port and fail fast when the proxy target is down.
- Keep an error boundary around data fetching that distinguishes 'invalid JSON' (transport) from status errors (API logic).
- Never point the dashboard at a static file server; only the Axum process serves /api/v1.
When it happens
Trigger: Calling any llmfit-web API helper while the page is served from something other than the llmfit Axum server (Vite dev server without a proxy for /api/v1, a static file server, or the wrong port); a reverse proxy or gateway returning an HTML 502/503/504 page; a truncated or empty response body from a crashed handler; a JSON body with a wrong Content-Type plus malformed content.
Common situations: Running `npm run dev` against a backend that is stopped or on a different port; the llmfit server restarted while the dashboard had a poll in flight; corporate proxy/VPN injecting an HTML interstitial; hitting /api/v1/* on a port that serves the built assets but not the API router.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
AI-assisted analysis of AlexsJones/llmfit@a9ac7ed91c (2026-08-16).
Data as JSON: /api/errors/7cea1cca77dfd860.
Report an issue: GitHub.