{"record":{"id":"c3608b3fc6a4181c","repo":"affaan-m/ECC","slug":"expected-json-response-from-control-pane-error","errorCode":null,"errorMessage":"Expected JSON response from control pane: ${error.message}","messagePattern":"Expected JSON response from control pane: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/control-pane/ui.js","lineNumber":444,"sourceCode":"      const target = $(targetSelector);\n      if (!target) return;\n      target.hidden = false;\n      target.textContent = formatError(error);\n    }\n\n    function clearError(targetSelector) {\n      const target = $(targetSelector);\n      if (!target) return;\n      target.hidden = true;\n      target.textContent = '';\n    }\n\n    async function readJsonResponse(response) {\n      let payload;\n      try {\n        payload = await response.json();\n      } catch (error) {\n        throw new Error('Expected JSON response from control pane: ' + error.message);\n      }\n\n      if (!response.ok) {\n        const detail = payload && payload.error ? payload.error : response.status + ' ' + response.statusText;\n        throw new Error(detail);\n      }\n\n      return payload;\n    }\n\n    function statePill(stateName) {\n      const state = String(stateName || 'unknown');\n      const klass = ['running', 'done'].includes(state)\n        ? 'good'\n        : ['failed', 'blocked'].includes(state)\n          ? 'bad'\n          : ['pending', 'ready'].includes(state)\n            ? 'warn'","sourceCodeStart":426,"sourceCodeEnd":462,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/control-pane/ui.js#L426-L462","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npayload = await response.json();\n// after\nconst text = await response.text();\ntry { payload = text ? JSON.parse(text) : null; }\ncatch (error) { throw new Error('Expected JSON response from control pane: ' + error.message); }","handlingStrategy":"try-catch","validationCode":"const ct = response.headers.get('content-type') || '';\nif (!ct.includes('application/json')) throw new Error('Non-JSON response: ' + ct);","typeGuard":"function isJsonBody(res) { return (res.headers.get('content-type') || '').includes('application/json'); }","tryCatchPattern":"try { const result = await readJsonResponse(response); } catch (e) {\n  if (String(e.message).startsWith('Expected JSON response')) {\n    console.error('Control pane returned non-JSON body; check server/proxy logs', e.message);\n  } else throw e;\n}","preventionTips":["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."],"tags":["fetch","json-parsing","control-pane"],"backgroundTag":"invalid-json-response","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}