affaan-m/ECC · error · Error
Expected JSON response from control pane: ${error.message}
Error message
Expected JSON response from control pane: ${error.message} What it means
In the control-pane browser UI, readJsonResponse() expects every /api response to be JSON. When response.json() fails to parse (the server or an intermediary returned HTML, plain text, or an empty body), it rethrows as 'Expected JSON response from control pane: <parse error>'. A non-2xx response with a valid JSON error payload instead surfaces payload.error or the status line.
Source
Thrown at scripts/lib/control-pane/ui.js:444
const target = $(targetSelector);
if (!target) return;
target.hidden = false;
target.textContent = formatError(error);
}
function clearError(targetSelector) {
const target = $(targetSelector);
if (!target) return;
target.hidden = true;
target.textContent = '';
}
async function readJsonResponse(response) {
let payload;
try {
payload = await response.json();
} catch (error) {
throw new Error('Expected JSON response from control pane: ' + error.message);
}
if (!response.ok) {
const detail = payload && payload.error ? payload.error : response.status + ' ' + response.statusText;
throw new Error(detail);
}
return payload;
}
function statePill(stateName) {
const state = String(stateName || 'unknown');
const klass = ['running', 'done'].includes(state)
? 'good'
: ['failed', 'blocked'].includes(state)
? 'bad'
: ['pending', 'ready'].includes(state)
? 'warn'View on GitHub (pinned to 06c5e118c4)
Solutions
- Open DevTools Network, select the failing request, and read the raw response body to see what actually came back
- Configure the proxy so /api/* passes through to the control-pane server untouched
- Restart the control-pane server and hard-reload the UI so UI and server versions match
Example fix
// before
payload = await response.json(); // throws on HTML error pages
// after
const text = await response.text();
let payload;
try { payload = JSON.parse(text); }
catch { throw new Error('Non-JSON response (HTTP ' + response.status + '): ' + text.slice(0, 120)); } Defensive patterns
Strategy: try-catch
Validate before calling
const contentType = response.headers.get('content-type') || '';
if (!contentType.includes('application/json')) {
throw new Error('Unexpected content-type for API call: ' + contentType);
} Try / catch
let payload;
const text = await response.text();
try {
payload = JSON.parse(text);
} catch (error) {
throw new Error('Non-JSON response (HTTP ' + response.status + '): ' + text.slice(0, 200));
}
if (!response.ok) throw new Error(payload && payload.error ? payload.error : response.status + ' ' + response.statusText); Prevention
- Serve the control-pane UI and API from the same origin
- Do not put HTML error pages in front of /api routes
- Hard-reload the UI after upgrading the server
When it happens
Trigger: fetch('/api/work-items') returns an nginx/caddy 502 HTML error page, a proxy auth redirect, or the SPA index.html because the route 404'd — the body is not JSON, so parsing throws inside readJsonResponse().
Common situations: Control pane accessed through a reverse proxy that intercepts errors; server crashed or restarted mid-request; stale cached UI hitting an older server without the endpoint.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- request failed
- Invalid ECC repo root: unreadable package.json at ${packageJ
- Failed to read ${filePath}: ${error.message}
- Invalid legacy Codex sync state at ${statePath}
- Unknown control-pane action: ${actionId}
AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18).
Data as JSON: /api/errors/c3608b3fc6a4181c.
Report an issue: GitHub.