{"record":{"id":"039b7a0d8f7ac240","repo":"MemPalace/mempalace","slug":"daemon-returned-non-json-response-raw-200-r","errorCode":null,"errorMessage":"daemon returned non-JSON response: {raw[:200]!r}","messagePattern":"daemon returned non-JSON response: (.+?)","errorType":"exception","errorClass":"DaemonError","httpStatus":null,"severity":"error","filePath":"mempalace/daemon.py","lineNumber":1173,"sourceCode":"                raw = resp.read().decode(\"utf-8\")\n        except urlerror.HTTPError as exc:\n            raw = exc.read().decode(\"utf-8\", errors=\"replace\")\n            try:\n                payload = json.loads(raw)\n            except json.JSONDecodeError:\n                payload = {\"error\": raw or str(exc)}\n            raise DaemonError(str(payload.get(\"error\", exc))) from exc\n        except OSError as exc:\n            raise DaemonError(str(exc)) from exc\n        if not raw:\n            return {}\n        try:\n            return json.loads(raw)\n        except json.JSONDecodeError as exc:\n            # A 2xx response with a non-JSON body (empty 200, truncated write,\n            # proxy HTML) shouldn't surface as a bare JSONDecodeError to callers\n            # that only know how to handle DaemonError.\n            raise DaemonError(f\"daemon returned non-JSON response: {raw[:200]!r}\") from exc\n\n    def health(self, *, timeout: float = 5.0) -> dict[str, Any]:\n        return self.request(\"GET\", \"/health\", timeout=timeout)\n\n    def submit(\n        self,\n        kind: str,\n        payload: dict[str, Any],\n        *,\n        dedupe_key: str | None = None,\n        priority: int = 0,\n    ) -> dict[str, Any]:\n        return self.request(\n            \"POST\",\n            \"/jobs\",\n            {\"kind\": kind, \"payload\": payload, \"dedupe_key\": dedupe_key, \"priority\": priority},\n        )[\"job\"]\n","sourceCodeStart":1155,"sourceCodeEnd":1191,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/daemon.py#L1155-L1191","documentation":"DaemonError from DaemonClient.request (mempalace/daemon.py:1173): the daemon returned a 2xx response whose body is not valid JSON (json.loads raised JSONDecodeError). This covers empty 200s, truncated responses, and proxy/servlet HTML. It is wrapped so callers that only handle DaemonError get a uniform error type.","triggerScenarios":"A proxy or middlebox intercepting the loopback request and returning HTML; the daemon killed mid-response leaving a truncated body; an empty 200 from an edge case in the handler; anything between the client and server mutating the body.","commonSituations":"System-wide HTTP interception (corporate proxy env vars) despite the no-proxy opener covering most cases; daemon crash during a long response; OS-level security software tampering with loopback traffic; version skew where a route exists but returns an empty body.","solutions":["Check daemon liveness (client.health()) and the daemon log; restart if it crashed mid-response.","Ensure no proxy/middleman touches 127.0.0.1 (the client already builds a no-proxy opener; keep env free of forced proxies).","Update/align client and daemon versions so both sides agree on which routes return JSON.","Retry the request once — truncated responses from a crashed daemon are not persistent once restarted."],"exampleFix":"# before\ntry:\n    result = client.request(\"POST\", \"/jobs\", payload)\nexcept json.JSONDecodeError:\n    ...  # never caught; crashes caller\n\n# after\ntry:\n    result = client.request(\"POST\", \"/jobs\", payload)\nexcept DaemonError as exc:\n    logger.warning(\"daemon call failed: %s\", exc)  # includes non-JSON case","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"from mempalace.daemon import DaemonError\n\nfor attempt in range(2):\n    try:\n        return client.request(\"POST\", \"/jobs\", payload)\n    except DaemonError as exc:\n        if \"non-JSON\" in str(exc) and attempt == 0:\n            client = ensure_client(palace_path)  # daemon may have crashed; restart once\n            continue\n        raise","preventionTips":["Keep loopback traffic free of proxies and middlemen.","Catch DaemonError uniformly at client boundaries; it wraps protocol failures too.","Health-check the daemon before long request batches."],"tags":["daemon","http","json","protocol"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}