{"record":{"id":"701231a2399c3a20","repo":"affaan-m/ECC","slug":"expected-json-response-from-control-pane","errorCode":null,"errorMessage":"Expected JSON response from control pane: ","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/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/control-pane/ui.js#L426-L462","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst payload = await response.json();\n\n// after — fail with the real body when it isn't JSON\nconst text = await response.text();\nlet payload;\ntry { payload = JSON.parse(text); }\ncatch (e) { throw new Error(`Expected JSON from ${response.url} (status ${response.status}, ct ${response.headers.get('content-type')}): ${text.slice(0, 200)}`); }","handlingStrategy":"try-catch","validationCode":"const ct = response.headers.get('content-type') || '';\nif (!ct.includes('application/json')) {\n  const body = await response.text();\n  throw new Error(`Non-JSON response (ct=${ct}, status=${response.status}): ${body.slice(0,200)}`);\n}","typeGuard":"function isJsonResponse(response) {\n  const ct = response.headers.get('content-type') || '';\n  return ct.includes('application/json');\n}","tryCatchPattern":"try {\n  await readJsonResponse(response);\n} catch (e) {\n  if (/Expected JSON response/.test(e.message)) {\n    // server returned non-JSON; surface status/content-type for diagnosis\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["control-pane","http","json","api-contract"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}