{"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/06c5e118c4d3e6c3b7f9445f973a2194c82de193/scripts/lib/control-pane/ui.js#L426-L462","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","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"],"exampleFix":"// before\npayload = await response.json(); // throws on HTML error pages\n// after\nconst text = await response.text();\nlet payload;\ntry { payload = JSON.parse(text); }\ncatch { throw new Error('Non-JSON response (HTTP ' + response.status + '): ' + text.slice(0, 120)); }","handlingStrategy":"try-catch","validationCode":"const contentType = response.headers.get('content-type') || '';\nif (!contentType.includes('application/json')) {\n  throw new Error('Unexpected content-type for API call: ' + contentType);\n}","typeGuard":null,"tryCatchPattern":"let payload;\nconst text = await response.text();\ntry {\n  payload = JSON.parse(text);\n} catch (error) {\n  throw new Error('Non-JSON response (HTTP ' + response.status + '): ' + text.slice(0, 200));\n}\nif (!response.ok) throw new Error(payload && payload.error ? payload.error : response.status + ' ' + response.statusText);","preventionTips":["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"],"tags":["control-pane","fetch","json","browser"],"backgroundTag":"invalid-json-response","analyzedSha":"06c5e118c4d3e6c3b7f9445f973a2194c82de193","analyzedAt":"2026-08-18T11:27:13.915Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}