From 088a4efaa3aa5e6325c2cba2a6366fc5550743bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Mon, 4 May 2026 14:28:10 +0100 Subject: [PATCH] =?UTF-8?q?fix(cli):=201.31.2=20=E2=80=94=20daemon=20paths?= =?UTF-8?q?=20no=20longer=20follow=20per-session=20CLAUDEMESH=5FCONFIG=5FD?= =?UTF-8?q?IR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real production bug observed in 1.31.0 / 1.31.1: every CLI verb 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 answering direct UDS probes in 10ms. Root cause: claudemesh launch exports CLAUDEMESH_CONFIG_DIR to a per-session tmpdir so joined-mesh state and the IPC session token stay isolated. DAEMON_PATHS read from the same env, so inside a launched session the CLI looked for daemon.sock at /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 live at ~/.claudemesh/daemon/ regardless of overlays. Pin DAEMON_PATHS.DAEMON_DIR to that location. New CLAUDEMESH_DAEMON_DIR override is preserved for tests and multi-daemon dev setups. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/cli/CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ apps/cli/package.json | 2 +- apps/cli/src/daemon/paths.ts | 25 +++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 3 deletions(-) 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"); },