#2475

Foreground daemon emits no lifecycle logging (empty launchd StandardOutPath)

Openmustafaturksavasopened 1mo ago

Foreground daemon emits no lifecycle logging (empty launchd StandardOutPath)

Environment

  • MemPalace 3.8.0 (mempalace --version -> MemPalace 3.8.0; package metadata and mempalace.version.__version__ agree)
  • STILL PRESENT ON LATEST: v3.9.0 (2026-08-31) is the current release, and mempalace/daemon.py is byte-identical between v3.8.0 and v3.9.0 — the log_message no-op and the serve_forever path are unchanged there.
  • Platform: macOS 26.6.2 (Sequoia-class), python3.9 (uv tool install)
  • Launch form: launchd agent ai.mempalace.daemon (/Users/hermes/Library/LaunchAgents/ai.mempalace.daemon.plist) running /Users/hermes/.local/bin/mempalace daemon start --foreground, with StandardOutPath=/Users/hermes/.mempalace/logs/daemon.log and StandardErrorPath=/Users/hermes/.mempalace/logs/daemon.error.log.

Observed

  • StandardOutPath stays empty indefinitely while the daemon runs:
    • /Users/hermes/.mempalace/logs/daemon.log -> 0 bytes (mtime 2026-08-11)
    • /Users/hermes/.mempalace/logs/daemon.error.log -> 1848 bytes, 8 lines, all of them prior startup errors (e.g. "writable daemon startup refused: another writer owns local backend 'chroma'"), none of them lifecycle output from a healthy run. So stderr proves the redirect works; the healthy foreground path simply never writes to it.
  • mempalace daemon status reports the daemon is running (PID alive, endpoint reachable), so the process is healthy — it simply emits no output.

Expected

  • In --foreground mode the server should log at least startup and lifecycle events to stderr (or stdout), so a supervisor that captures via StandardOutPath/StandardErrorPath actually receives log lines.

Cause

  • run_server (mempalace/daemon.py:872) emits no intentional startup or HTTP access logging in foreground mode. Its only output hook is the HTTP handler's log_message, which is a deliberate no-op:
    # mempalace/daemon.py:920-921
    def log_message(self, fmt, *args):  # pragma: no cover - stdlib access logging noise
        return
    No other code path in run_server prints, so with stdout redirected to the launchd log path that file stays empty for the life of a healthy run.
  • Contrast: non-foreground mode spawns the child with stdout/stderr bound to a per-daemon file via _detached_kwargs (mempalace/daemon.py:1257-1260) and kwargs = _detached_kwargs(sd / "daemon.log") (mempalace/daemon.py:1332). That path works; only foreground is silent.
  • The daemon does NOT import mcp_server, so the MCP stdio guard there is unrelated and is not the cause.

Proposed fix

Make the foreground path emit timestamped, level-gated lines to stderr:

--- a/mempalace/daemon.py
+++ b/mempalace/daemon.py
@@ run_server (foreground), after `runtime = DaemonRuntime(...)`:
+        import sys, logging
+        logging.basicConfig(stream=sys.stderr, level=logging.INFO,
+                            format="%(asctime)s %(levelname)s %(message)s")
+        log = logging.getLogger("mempalace.daemon")
+        log.info("daemon listening on %s", runtime.endpoint)

Optionally replace the _Handler.log_message no-op with logging.getLogger("mempalace.daemon.http").info(fmt, *args) so HTTP access logging also flows to stderr when level permits. Keep non-foreground behavior unchanged (it already has its own log file).

Workaround

  • Liveness/health is observable via mempalace daemon status (no log needed).
  • For actual on-disk logs, run without --foreground: the non-foreground path writes to /Users/hermes/.mempalace/daemon/<sha>/daemon.log.

Activity

0

No activity yet.