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

  1. Open DevTools Network, select the failing request, and read the raw response body to see what actually came back
  2. Configure the proxy so /api/* passes through to the control-pane server untouched
  3. 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

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


AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18). Data as JSON: /api/errors/c3608b3fc6a4181c. Report an issue: GitHub.