affaan-m/ECC · error · Error
Expected JSON response from control pane
Error message
Expected JSON response from control pane: ${error.message} What it means
readJsonResponse parses the fetch response body as JSON. When response.json() rejects (empty body, HTML error page, truncated stream), it rethrows as 'Expected JSON response from control pane: <reason>'. This distinguishes parse failures from HTTP status errors, which are handled separately below.
Solutions
- Inspect error.message suffix for the underlying parse failure (e.g. 'Unexpected end of JSON input' means empty/truncated body).
- Check the server logs for the /api/work-items request — a 5xx with HTML body is the usual culprit.
- Confirm the endpoint route exists and always returns NextResponse.json(...), never res.end() or a redirect.
- Retry the action once the server is healthy; this is typically transient infrastructure, not client code.
Example fix
// before
payload = await response.json();
// after
const text = await response.text();
try { payload = text ? JSON.parse(text) : null; }
catch (error) { throw new Error('Expected JSON response from control pane: ' + error.message); } Defensive patterns
Strategy: try-catch
Validate before calling
const ct = response.headers.get('content-type') || '';
if (!ct.includes('application/json')) throw new Error('Non-JSON response: ' + ct); Type guard
function isJsonBody(res) { return (res.headers.get('content-type') || '').includes('application/json'); } Try / catch
try { const result = await readJsonResponse(response); } catch (e) {
if (String(e.message).startsWith('Expected JSON response')) {
console.error('Control pane returned non-JSON body; check server/proxy logs', e.message);
} else throw e;
} Prevention
- Check response content-type before parsing JSON.
- Keep the API routes returning res.json(...) on every path, including errors.
- Watch for proxies (nginx/CDN) replacing bodies with HTML error pages — monitor 5xx rates.
- Refresh UI state after server restarts before re-issuing actions.
When it happens
Trigger: Calling any control-pane action (e.g. window.eccClaimItem posting to /api/work-items/...) where the server returns a non-JSON body: empty response, HTML 502 page from a proxy, or an aborted/stream error mid-read.
Common situations: Dev server restarting mid-request, a reverse proxy (nginx/cloudflare) returning an HTML error page, server route crashing before writing a body, or network interruption during response streaming.
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
- empty response
- HTTP + res.status
- (payload.error || status + ' ' + statusText)
- request failed
- ${result.error || 'request failed'}
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/c3608b3fc6a4181c.
Report an issue: GitHub.
Appendix: 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 8321021c54)