affaan-m/ECC · error · Error
Expected JSON response from control pane:
Error message
Expected JSON response from control pane:
What it means
Thrown by readJsonResponse when the control-pane HTTP response body cannot be parsed as JSON. The control-pane API is contractually expected to return application/json on every route (including errors); a non-JSON body violates that contract. The wrapped error.message comes from the underlying JSON parser and usually indicates an empty body, an HTML error page, or a proxy/HTML 404 intercepting the request.
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 01e15490f0)
Solutions
- Confirm the control-pane server is running and reachable (curl -i http://localhost:<port>/api/health) and that it returns Content-Type: application/json.
- Inspect the raw response body/status before parsing — log response.status, response.headers.get('content-type'), and await response.text() to see what was actually returned.
- Fix or register the missing route so the control-pane returns JSON for that path.
- If a proxy sits in front, configure it to pass through /api/* untouched and serve its own errors as JSON.
Example fix
// before
const payload = await response.json();
// after — fail with the real body when it isn't JSON
const text = await response.text();
let payload;
try { payload = JSON.parse(text); }
catch (e) { throw new Error(`Expected JSON from ${response.url} (status ${response.status}, ct ${response.headers.get('content-type')}): ${text.slice(0, 200)}`); } Defensive patterns
Strategy: try-catch
Validate before calling
const ct = response.headers.get('content-type') || '';
if (!ct.includes('application/json')) {
const body = await response.text();
throw new Error(`Non-JSON response (ct=${ct}, status=${response.status}): ${body.slice(0,200)}`);
} Type guard
function isJsonResponse(response) {
const ct = response.headers.get('content-type') || '';
return ct.includes('application/json');
} Try / catch
try {
await readJsonResponse(response);
} catch (e) {
if (/Expected JSON response/.test(e.message)) {
// server returned non-JSON; surface status/content-type for diagnosis
}
throw e;
} Prevention
- Always check content-type before parsing responses from any HTTP API.
- Run the control-pane server with health checks so callers detect crashes before sending requests.
- Log response.status and the first bytes of the body when parsing fails, to distinguish proxy HTML from empty bodies.
When it happens
Trigger: Calling any control-pane endpoint (e.g. POST /api/work-items/<id>/claim via postWorkItem) whose response body is empty or HTML; a reverse proxy returning an HTML 502/404; the local server crashed mid-response; a route is missing so the static-file server returns index.html.
Common situations: The control-pane server is not running or crashed (connection returns HTML); a request was sent before the server finished booting; the URL path was wrong and a catch-all served HTML; a corporate proxy injected an HTML block page; CORS/preflight returned HTML.
Related errors
- Unknown control-pane action: ${actionId}
- request failed
- ECC_PROJECT_DIR must be a child path within /workspace.
- Unknown argument: ${arg}
- ${source} is not valid JSON: ${error.message}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/701231a2399c3a20.
Report an issue: GitHub.