MemPalace/mempalace · error · DaemonError

daemon endpoint pid is not alive

Error message

daemon endpoint pid is not alive

What it means

DaemonError from DaemonClient.__init__ (mempalace/daemon.py:1117): endpoint.json records a pid, but _pid_alive() reports that process no longer exists. The daemon is dead; the endpoint is stale. Critically, the client refuses to proceed because the dead daemon's port may since have been reused by an unrelated process, and sending the bearer token there would leak it — so the token is deliberately not read before this check.

Source

Thrown at mempalace/daemon.py:1117

            os.environ.pop(key, None)
        else:
            os.environ[key] = value


class DaemonClient:
    def __init__(self, palace_path: str):
        self.palace_path = canonical_palace_path(palace_path)
        endpoint = _read_endpoint(self.palace_path)
        port = endpoint.get("port")
        if port is None:
            raise DaemonError("daemon endpoint missing port")
        # Don't read the token until we trust the endpoint points at a live
        # process we started: a stale endpoint whose pid is dead may have its
        # port reused by an unrelated process, and we must not send our bearer
        # token there.
        pid = endpoint.get("pid")
        if pid is not None and not _pid_alive(int(pid)):
            raise DaemonError("daemon endpoint pid is not alive")
        self.token = read_token(self.palace_path)
        self.host = endpoint.get("host") or HOST
        self.port = int(port)
        # The daemon is always on 127.0.0.1, so a request must never go through
        # an HTTP proxy. Building an opener with an empty ProxyHandler bypasses
        # urllib's proxy discovery entirely. On macOS that discovery
        # (urllib.request._scproxy, via the SystemConfiguration framework) runs
        # on the first request to any host and is NOT bounded by the per-request
        # timeout — on a CI runner with no network it can hang for tens of
        # seconds, which looks exactly like the daemon never came up. A no-proxy
        # opener is the correct production choice here and also removes that hang.
        self._opener = urlrequest.build_opener(urlrequest.ProxyHandler({}))

    @property
    def base_url(self) -> str:
        return f"http://{self.host}:{self.port}"

    def request(

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Remove the stale endpoint.json and start a fresh daemon via start_daemon/ensure_client.
  2. Prefer ensure_client(palace_path) or get_client_if_running() over raw DaemonClient() — they handle restart and None-fallback correctly.
  3. If the daemon should be alive, check the pid file, daemon log, and port to diagnose the crash.

Example fix

# before
client = DaemonClient(palace_path)  # stale endpoint, pid dead

# after
client = get_client_if_running(palace_path) or start_daemon(palace_path)
Defensive patterns

Strategy: fallback

Validate before calling

import json
from mempalace import daemon

def daemon_alive(palace_path: str) -> bool:
    try:
        ep = json.loads(daemon.endpoint_path(palace_path).read_text())
    except (OSError, json.JSONDecodeError):
        return False
    pid = ep.get("pid")
    return pid is not None and daemon._pid_alive(int(pid))

Try / catch

from mempalace.daemon import DaemonError, get_client_if_running, start_daemon

client = get_client_if_running(palace_path)  # None when pid is dead
if client is None:
    daemon.endpoint_path(palace_path).unlink(missing_ok=True)
    client = start_daemon(palace_path)

Prevention

When it happens

Trigger: Daemon crashed or was killed after writing endpoint.json; machine rebooted leaving a stale endpoint; pid recycled by the OS to another process is the danger case being guarded; constructing DaemonClient long after the daemon exited.

Common situations: Hooks firing after a laptop sleep/reboot where the daemon died; CI runners between jobs; daemon OOM-killed; get_client_if_running normally swallows this as None, so a direct DaemonClient() call exposes it.

Related errors


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