MemPalace/mempalace · error · DaemonError

daemon endpoint not found

Error message

daemon endpoint not found

What it means

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.

Source

Thrown at mempalace/daemon.py:182

def endpoint_path(palace_path: str) -> Path:
    return state_dir(palace_path) / "endpoint.json"


def pid_path(palace_path: str) -> Path:
    return state_dir(palace_path) / "pid"


def queue_path(palace_path: str) -> Path:
    return state_dir(palace_path) / "queue.sqlite3"


def _read_endpoint(palace_path: str) -> dict[str, Any]:
    try:
        with open(endpoint_path(palace_path), encoding="utf-8") as fh:
            return json.load(fh)
    except (OSError, json.JSONDecodeError) as exc:
        raise DaemonError("daemon endpoint not found") from exc


def _pid_alive_windows(pid: int) -> bool:
    """Liveness probe for Windows that never sends a console control event.

    ``os.kill(pid, 0)`` is NOT a harmless existence check on Windows: signal 0
    is ``signal.CTRL_C_EVENT``, so Python routes it to
    ``GenerateConsoleCtrlEvent`` and sends a Ctrl-C to the target's process
    group instead of probing the pid. On a process with an attached console
    (e.g. a CI runner) that Ctrl-C is delivered back to *this* interpreter and
    surfaces as a spurious ``KeyboardInterrupt`` — exactly the hang seen when
    ``DaemonClient`` polled a same-process endpoint. Probe via the Win32 process
    handle API instead, which has no signalling side effects.
    """
    import ctypes
    from ctypes import wintypes

    SYNCHRONIZE = 0x00100000

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. If no daemon is meant to be running, start one via ensure_client/start_daemon instead of constructing DaemonClient directly.
  2. If a daemon should be running, check the daemon log for a startup crash, then delete the stale endpoint.json and restart.
  3. If endpoint.json is corrupt (truncated JSON), remove it and restart the daemon to rewrite it atomically.
  4. Verify state_dir(palace_path) points where you expect (canonical path, no symlinks/moves).

Example fix

# before
client = DaemonClient(palace_path)  # endpoint missing/corrupt

# after
client = get_client_if_running(palace_path)  # returns None instead of raising when absent
if client is None:
    client = start_daemon(palace_path)
Defensive patterns

Strategy: try-catch

Validate before calling

from mempalace import daemon

def endpoint_readable(palace_path: str) -> bool:
    p = daemon.endpoint_path(palace_path)
    try:
        json.loads(p.read_text(encoding="utf-8"))
        return True
    except (OSError, json.JSONDecodeError):
        return False

Try / catch

from mempalace.daemon import DaemonError, get_client_if_running

client = get_client_if_running(palace_path)  # returns None when absent/stale
if client is None:
    client = start_daemon(palace_path)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15). Data as JSON: /api/errors/7ad433e3046f326e. Report an issue: GitHub.