MemPalace/mempalace · error · DaemonError
daemon returned non-JSON response: {raw[:200]!r}
Error message
daemon returned non-JSON response: {raw[:200]!r} What it means
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.
Source
Thrown at mempalace/daemon.py:1173
raw = resp.read().decode("utf-8")
except urlerror.HTTPError as exc:
raw = exc.read().decode("utf-8", errors="replace")
try:
payload = json.loads(raw)
except json.JSONDecodeError:
payload = {"error": raw or str(exc)}
raise DaemonError(str(payload.get("error", exc))) from exc
except OSError as exc:
raise DaemonError(str(exc)) from exc
if not raw:
return {}
try:
return json.loads(raw)
except json.JSONDecodeError as exc:
# A 2xx response with a non-JSON body (empty 200, truncated write,
# proxy HTML) shouldn't surface as a bare JSONDecodeError to callers
# that only know how to handle DaemonError.
raise DaemonError(f"daemon returned non-JSON response: {raw[:200]!r}") from exc
def health(self, *, timeout: float = 5.0) -> dict[str, Any]:
return self.request("GET", "/health", timeout=timeout)
def submit(
self,
kind: str,
payload: dict[str, Any],
*,
dedupe_key: str | None = None,
priority: int = 0,
) -> dict[str, Any]:
return self.request(
"POST",
"/jobs",
{"kind": kind, "payload": payload, "dedupe_key": dedupe_key, "priority": priority},
)["job"]
View on GitHub (pinned to 06cb6987f0)
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.
Example fix
# before
try:
result = client.request("POST", "/jobs", payload)
except json.JSONDecodeError:
... # never caught; crashes caller
# after
try:
result = client.request("POST", "/jobs", payload)
except DaemonError as exc:
logger.warning("daemon call failed: %s", exc) # includes non-JSON case Defensive patterns
Strategy: retry
Try / catch
from mempalace.daemon import DaemonError
for attempt in range(2):
try:
return client.request("POST", "/jobs", payload)
except DaemonError as exc:
if "non-JSON" in str(exc) and attempt == 0:
client = ensure_client(palace_path) # daemon may have crashed; restart once
continue
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- daemon endpoint not found
- invalid Content-Length
- request body too large
- Malformed response from {url}: {e}
- operator {op!r} not supported by chroma backend
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/039b7a0d8f7ac240.
Report an issue: GitHub.