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
- 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.
- Route all writes through the single owner instead of starting a second one — point hooks/CLI at the running daemon.
- If the owner is a zombie/orphan, kill its pid (verify it is really a mempalace process first) and retry.
- 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
- Designate exactly one writable owner per palace (daemon or MCP), enforce it in startup scripts.
- On startup races, retry get_client_if_running before attempting to start again.
- Clean up orphaned mempalace processes after crashes before restarting.
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
- daemon token not found for {palace_path}
- daemon endpoint missing port
- daemon endpoint pid is not alive
- timed out waiting for job {job_id}
- daemon exited during startup with code {proc.returncode}
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/67a08bd61fec1545.
Report an issue: GitHub.