{"record":{"id":"7ad433e3046f326e","repo":"MemPalace/mempalace","slug":"daemon-endpoint-not-found","errorCode":null,"errorMessage":"daemon endpoint not found","messagePattern":"daemon endpoint not found","errorType":"exception","errorClass":"DaemonError","httpStatus":null,"severity":"error","filePath":"mempalace/daemon.py","lineNumber":182,"sourceCode":"\ndef endpoint_path(palace_path: str) -> Path:\n    return state_dir(palace_path) / \"endpoint.json\"\n\n\ndef pid_path(palace_path: str) -> Path:\n    return state_dir(palace_path) / \"pid\"\n\n\ndef queue_path(palace_path: str) -> Path:\n    return state_dir(palace_path) / \"queue.sqlite3\"\n\n\ndef _read_endpoint(palace_path: str) -> dict[str, Any]:\n    try:\n        with open(endpoint_path(palace_path), encoding=\"utf-8\") as fh:\n            return json.load(fh)\n    except (OSError, json.JSONDecodeError) as exc:\n        raise DaemonError(\"daemon endpoint not found\") from exc\n\n\ndef _pid_alive_windows(pid: int) -> bool:\n    \"\"\"Liveness probe for Windows that never sends a console control event.\n\n    ``os.kill(pid, 0)`` is NOT a harmless existence check on Windows: signal 0\n    is ``signal.CTRL_C_EVENT``, so Python routes it to\n    ``GenerateConsoleCtrlEvent`` and sends a Ctrl-C to the target's process\n    group instead of probing the pid. On a process with an attached console\n    (e.g. a CI runner) that Ctrl-C is delivered back to *this* interpreter and\n    surfaces as a spurious ``KeyboardInterrupt`` — exactly the hang seen when\n    ``DaemonClient`` polled a same-process endpoint. Probe via the Win32 process\n    handle API instead, which has no signalling side effects.\n    \"\"\"\n    import ctypes\n    from ctypes import wintypes\n\n    SYNCHRONIZE = 0x00100000","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/daemon.py#L164-L200","documentation":"DaemonError from _read_endpoint() (mempalace/daemon.py:182): the endpoint.json file under the palace state dir could not be opened or parsed. Both OSError (missing/unreadable file) and json.JSONDecodeError (truncated or corrupt JSON, e.g. after a crash mid-write) are chained into this error. Endpoint discovery is the first step of DaemonClient construction, so no daemon interaction can proceed.","triggerScenarios":"Constructing DaemonClient or calling get_client_if_running when no daemon ever wrote endpoint.json; the daemon crashed while writing the endpoint file leaving partial JSON; the state dir was copied/moved without endpoint.json; reading permissions lost on the state dir.","commonSituations":"Hook runs on a fresh clone/machine before the first daemon start; a power loss or kill -9 during daemon startup corrupting endpoint.json; manual cleanup of state dirs; get_client_if_running normally converts this to None (daemon not running), so seeing it directly usually means a direct DaemonClient() call or a corrupted-file path.","solutions":["If no daemon is meant to be running, start one via ensure_client/start_daemon instead of constructing DaemonClient directly.","If a daemon should be running, check the daemon log for a startup crash, then delete the stale endpoint.json and restart.","If endpoint.json is corrupt (truncated JSON), remove it and restart the daemon to rewrite it atomically.","Verify state_dir(palace_path) points where you expect (canonical path, no symlinks/moves)."],"exampleFix":"# before\nclient = DaemonClient(palace_path)  # endpoint missing/corrupt\n\n# after\nclient = get_client_if_running(palace_path)  # returns None instead of raising when absent\nif client is None:\n    client = start_daemon(palace_path)","handlingStrategy":"try-catch","validationCode":"from mempalace import daemon\n\ndef endpoint_readable(palace_path: str) -> bool:\n    p = daemon.endpoint_path(palace_path)\n    try:\n        json.loads(p.read_text(encoding=\"utf-8\"))\n        return True\n    except (OSError, json.JSONDecodeError):\n        return False","typeGuard":null,"tryCatchPattern":"from mempalace.daemon import DaemonError, get_client_if_running\n\nclient = get_client_if_running(palace_path)  # returns None when absent/stale\nif client is None:\n    client = start_daemon(palace_path)","preventionTips":["Prefer get_client_if_running over raw DaemonClient for discovery.","Delete stale/corrupt endpoint.json on daemon shutdown failure and restart cleanly.","Atomic-write JSON state files (write temp + rename) if you create similar files."],"tags":["daemon","discovery","filesystem","json"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}