diff --git a/apps/cli/CHANGELOG.md b/apps/cli/CHANGELOG.md index e6fba60..9d696a5 100644 --- a/apps/cli/CHANGELOG.md +++ b/apps/cli/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## 1.31.2 (2026-05-04) — daemon paths no longer follow per-session CLAUDEMESH_CONFIG_DIR + +**Production bug observed in real installs:** every CLI verb invoked from +inside a `claudemesh launch`-spawned session printed + +``` +[claudemesh] warn service-managed daemon not responding within 8000ms +``` + +even when the launchd-managed daemon was healthy and responding to +direct probes in ~10 ms. + +Root cause: `claudemesh launch` exports `CLAUDEMESH_CONFIG_DIR` to a +per-session tmpdir so that joined-mesh state and the session IPC +token stay isolated from the host's shared config. `DAEMON_PATHS` +read its base directory from the same env var, so inside a launched +session the CLI looked for `daemon.sock` at e.g. +`/var/folders/.../claudemesh-XXXX/daemon/daemon.sock` — which never +exists. The CLI declared the daemon down, fell into the +service-managed wait branch, and timed out. + +The daemon is a per-machine singleton serving every session; its +files belong at `~/.claudemesh/daemon/` regardless of any per-session +overlay. Fix: pin `DAEMON_PATHS.DAEMON_DIR` to `~/.claudemesh/daemon/` +and ignore `CLAUDEMESH_CONFIG_DIR`. A new `CLAUDEMESH_DAEMON_DIR` +override is preserved for tests / multi-daemon dev setups; production +callers should never set it. + +After this fix, all CLI verbs from within a launched session take the +warm-path (~10 ms IPC) again instead of the cold path (~600-1200 ms). + ## 1.31.1 (2026-05-04) — hotfix: reaper stops blocking the daemon event loop 1.31.0 shipped a session reaper that called `execFileSync("ps")` diff --git a/apps/cli/package.json b/apps/cli/package.json index 932b7cf..95b0d63 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "claudemesh-cli", - "version": "1.31.1", + "version": "1.31.2", "description": "Peer mesh for Claude Code sessions — CLI + MCP server.", "keywords": [ "claude-code", diff --git a/apps/cli/src/daemon/paths.ts b/apps/cli/src/daemon/paths.ts index be55bca..f40d918 100644 --- a/apps/cli/src/daemon/paths.ts +++ b/apps/cli/src/daemon/paths.ts @@ -1,9 +1,30 @@ +import { homedir } from "node:os"; import { join } from "node:path"; -import { PATHS } from "~/constants/paths.js"; +/** + * Daemon paths intentionally do NOT honor `CLAUDEMESH_CONFIG_DIR`. + * + * `claudemesh launch` sets `CLAUDEMESH_CONFIG_DIR` to a per-session + * tmpdir so that joined-mesh state, last-used selections, and the + * IPC session token stay isolated from the host's shared config. + * The daemon, however, is a single per-machine process serving every + * launched session — its socket, pid file, on-disk outbox, and SQLite + * stores all live under `~/.claudemesh/daemon/`. Letting them inherit + * the per-session tmpdir would point each CLI invocation inside a + * launched session at a daemon socket that doesn't exist, force the + * cold path, and surface as "service-managed daemon not responding + * within 8000ms" (1.31.0 regression observed in real install). + * + * `CLAUDEMESH_DAEMON_DIR` exists as an explicit override for tests + * and for the rare case of running multiple daemon instances side by + * side (e.g. integration tests). Production callers should never set + * it. + */ +const DAEMON_DIR_ROOT = + process.env.CLAUDEMESH_DAEMON_DIR || join(homedir(), ".claudemesh", "daemon"); export const DAEMON_PATHS = { - get DAEMON_DIR() { return join(PATHS.CONFIG_DIR, "daemon"); }, + get DAEMON_DIR() { return DAEMON_DIR_ROOT; }, get PID_FILE() { return join(this.DAEMON_DIR, "daemon.pid"); }, get SOCK_FILE() { return join(this.DAEMON_DIR, "daemon.sock"); }, get TOKEN_FILE() { return join(this.DAEMON_DIR, "local-token"); },