MemPalace/mempalace · critical · DaemonError

daemon exited during startup with code {proc.returncode}

Error message

daemon exited during startup with code {proc.returncode}

What it means

DaemonError raised in start_daemon's readiness loop (mempalace/daemon.py:1345): the spawned daemon subprocess exited (proc.poll() is not None) before becoming healthy, with its exit code embedded in the message. The child is killed and reaped by the surrounding handler so no orphan holds the port/token/queue. Common underlying causes are visible in the daemon's log file.

Source

Thrown at mempalace/daemon.py:1345

    if backend:
        cmd.extend(["--backend", backend])
    env = os.environ.copy()
    if STATE_ROOT_ENV in os.environ:
        env[STATE_ROOT_ENV] = os.environ[STATE_ROOT_ENV]
    kwargs = _detached_kwargs(sd / "daemon.log")
    proc = None
    try:
        proc = subprocess.Popen(cmd, env=env, **kwargs)
    finally:
        log_fh = kwargs.get("stdout")
        if hasattr(log_fh, "close"):
            log_fh.close()
    try:
        deadline = time.monotonic() + timeout
        last_error = None
        while time.monotonic() < deadline:
            if proc.poll() is not None:
                raise DaemonError(f"daemon exited during startup with code {proc.returncode}")
            try:
                client = DaemonClient(palace_path)
                client.health()
                return client
            except DaemonError as exc:
                last_error = exc
                time.sleep(0.1)
        raise DaemonError(f"daemon did not become ready: {last_error}")
    except BaseException:
        # Readiness failed — don't leak an orphaned detached child holding the
        # port, token, queue, and log handle. Kill and reap it before raising.
        if proc is not None and proc.poll() is None:
            try:
                proc.kill()
                proc.wait()
            except Exception:  # noqa: BLE001 - cleanup best-effort
                pass
        raise

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Open the daemon log file (the path passed as stdout to Popen) and read the child's traceback — the exit code alone is secondary.
  2. Fix the root cause it shows (config validation error, port conflict, permissions, missing backend package).
  3. Verify no other daemon is already running (pid file, get_client_if_running) so the port is free.
  4. Retry start_daemon after fixing; the cleanup path guarantees no half-spawned child remains.

Example fix

# before
client = start_daemon(palace_path)  # exits code 1, opaque

# after
try:
    client = start_daemon(palace_path)
except DaemonError as exc:
    print(open(daemon_log_path).read())  # child traceback explains the exit code
Defensive patterns

Strategy: try-catch

Validate before calling

from mempalace.daemon import get_client_if_running

if get_client_if_running(palace_path) is None:
    # validate config loads cleanly before spawning the daemon
    from mempalace.config import load_config
    load_config(palace_path)

Try / catch

from mempalace.daemon import DaemonError

try:
    client = start_daemon(palace_path)
except DaemonError as exc:
    if "exited during startup" in str(exc):
        log = Path(daemon_log).read_text()  # child traceback explains the code
        raise RuntimeError(f"daemon crash:\n{log}") from exc
    raise

Prevention

When it happens

Trigger: Daemon process crashing at import/startup (bad config, missing dependency, unsupported backend); port already bound causing immediate exit; permission errors creating state files; a Python version mismatch in the spawned interpreter.

Common situations: First daemon start after a config edit that introduced error 200/202/203-class problems; upgrade left incompatible state; the log file handle (stdout redirect) contains the traceback; another daemon raced to bind the port.

Related errors


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