MemPalace/mempalace · critical · DaemonError

writable daemon startup refused: another writer owns local b

Error message

writable daemon startup refused: another writer owns local backend {resolved_backend!r} for {palace_path!r}; stop the existing writable MCP/direct/daemon owner, or route all writes through that owner

What it means

DaemonError raised in run_server (mempalace/daemon.py:898) when the resolved backend requires single-writer ownership (backend_requires_single_writer) and mine_palace_lock raises MineAlreadyRunning — i.e. another writable process (MCP server, direct CLI writer, or daemon) already holds the palace lock. The daemon refuses to start rather than risk concurrent writers corrupting a local backend like ChromaDB/SQLite.

Source

Thrown at mempalace/daemon.py:898

    if backend:
        os.environ["MEMPALACE_BACKEND_EXPLICIT"] = backend
        os.environ["MEMPALACE_BACKEND"] = backend
    # Privacy by architecture: tighten the umask to owner-only BEFORE the queue
    # DB is created. SQLite's WAL/SHM sidecars hold un-checkpointed verbatim
    # payloads and are (re)created with the process umask on every open/close
    # cycle, so the umask must already be tight when DaemonRuntime builds the
    # QueueStore (its _init_db opens the DB in WAL mode) — not only once the HTTP
    # server starts. Restored in the finally at the end of run_server.
    prev_umask = os.umask(0o077)
    runtime = None
    writer_lease = contextlib.ExitStack()
    try:
        resolved_backend = resolve_backend_name(palace_path, explicit=backend)
        if backend_requires_single_writer(resolved_backend):
            try:
                writer_lease.enter_context(mine_palace_lock(palace_path))
            except MineAlreadyRunning as exc:
                raise DaemonError(
                    "writable daemon startup refused: another writer owns "
                    f"local backend {resolved_backend!r} for {palace_path!r}; "
                    "stop the existing writable MCP/direct/daemon owner, or "
                    "route all writes through that owner"
                ) from exc

        token = ensure_token(palace_path)
        # Backend resolution above is only the ownership decision. Preserve
        # the caller's explicit/implicit distinction in queued payloads:
        # DaemonRuntime historically injects a backend only when one was
        # explicitly selected.
        runtime = DaemonRuntime(palace_path, backend=backend)
    except BaseException:
        writer_lease.close()
        _restore_server_process_state(previous_env, prev_umask)
        raise

    class _Handler(BaseHTTPRequestHandler):

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Find and stop the existing writer: check the daemon pid file and MCP processes (ps aux | grep mempalace), stop the writable MCP/direct owner, then retry daemon startup.
  2. Route all writes through the single owner instead of starting a second one — point hooks/CLI at the running daemon.
  3. If the owner is a zombie/orphan, kill its pid (verify it is really a mempalace process first) and retry.
  4. If you genuinely need concurrent processes, use a backend that doesn't require single-writer ownership.

Example fix

# before: second writer while MCP owns the palace
client = start_daemon(palace_path)  # DaemonError: startup refused

# after: stop the MCP/direct writer first, or reuse the running daemon
client = get_client_if_running(palace_path)
if client is None:
    client = start_daemon(palace_path)
Defensive patterns

Strategy: try-catch

Validate before calling

from mempalace.daemon import get_client_if_running

# reuse the running owner instead of starting a second writer
client = get_client_if_running(palace_path)
if client is None:
    # safe to attempt startup only when no owner answers
    ...

Try / catch

from mempalace.daemon import DaemonError

try:
    client = start_daemon(palace_path)
except DaemonError as exc:
    if "another writer owns" in str(exc):
        # stop the other owner or route writes through it; do not force a second daemon
        client = get_client_if_running(palace_path)
    else:
        raise

Prevention

When it happens

Trigger: Starting a second daemon (mempalace daemon start / start_daemon) while a writable MCP server or a direct write command holds the palace lock; a previous daemon that didn't shut down cleanly (orphan holding the lock); running CLI writes concurrently with daemon startup; a stale lock from a crashed process that still looks alive by pid.

Common situations: Claude Code MCP server running with write access while the user manually starts the daemon; CI jobs racing on the same palace directory; a forgotten background daemon; a wedged process after a laptop sleep/restore.

Related errors


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