diff --git a/apps/cli/CHANGELOG.md b/apps/cli/CHANGELOG.md index 1df47cf..0b4f87f 100644 --- a/apps/cli/CHANGELOG.md +++ b/apps/cli/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 1.30.2 (2026-05-04) — daemon service is multi-mesh by default + +`claudemesh install` was hardcoding `--mesh ` into the +launchd plist / systemd unit, which locked the daemon to a single +mesh and contradicted 1.26.0's multi-mesh design (one daemon attaches +to every joined mesh on boot). + +Net effect for users with more than one joined mesh: every CLI verb +against a non-primary mesh fell off the daemon path back to cold-WS +and re-handshakes a fresh broker connection on each call. Most +visible symptom is `[claudemesh] warn daemon spawn failed: socket did +not appear within 3000ms` when a launched session asks for peers in +a sibling mesh, plus `peer list --mesh foo` returning peers from +every attached mesh because the server-side filter never ran. + +Now: install drops the `--mesh` arg entirely so the unit launches +`claudemesh daemon up` (no flag), which attaches to every joined +mesh. `claudemesh daemon install-service --mesh ` is preserved +for users who want to pin to one mesh (CI, single-mesh hosts). + ## 1.30.1 (2026-05-04) — daemon install upgrade-safe + node-pinned Two install-path fixes that bit on first user upgrade: diff --git a/apps/cli/package.json b/apps/cli/package.json index e8900cd..de53d2e 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "claudemesh-cli", - "version": "1.30.1", + "version": "1.30.2", "description": "Peer mesh for Claude Code sessions — CLI + MCP server.", "keywords": [ "claude-code", diff --git a/apps/cli/src/commands/daemon.ts b/apps/cli/src/commands/daemon.ts index c904c56..52b7efa 100644 --- a/apps/cli/src/commands/daemon.ts +++ b/apps/cli/src/commands/daemon.ts @@ -190,13 +190,11 @@ async function runInstallService(opts: DaemonOptions): Promise { process.stderr.write(`unsupported platform: ${process.platform}\n`); return 2; } - if (!opts.mesh) { - process.stderr.write(`pass --mesh so the service knows which mesh to attach to\n`); - return 2; - } // Resolve the binary path. Prefer the running argv[0] when it's an // installed claudemesh binary; fall back to whichever `claudemesh` is - // first on PATH. + // first on PATH. --mesh is now optional: omit it to attach to every + // joined mesh (the 1.26.0 multi-mesh default); pass it to lock the + // unit to a single mesh for testing or single-mesh hosts. let binary = process.argv[1] ?? ""; if (!binary || /\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) { try { @@ -210,8 +208,8 @@ async function runInstallService(opts: DaemonOptions): Promise { try { const r = installService({ binaryPath: binary, - meshSlug: opts.mesh, - displayName: opts.displayName, + ...(opts.mesh ? { meshSlug: opts.mesh } : {}), + ...(opts.displayName ? { displayName: opts.displayName } : {}), }); if (opts.json) { process.stdout.write(JSON.stringify({ ok: true, ...r }) + "\n"); diff --git a/apps/cli/src/commands/install.ts b/apps/cli/src/commands/install.ts index f342bbf..f1cd2c4 100644 --- a/apps/cli/src/commands/install.ts +++ b/apps/cli/src/commands/install.ts @@ -545,23 +545,25 @@ export function runInstall(args: string[] = []): void { } let hasMeshes = false; - let primaryMesh: string | undefined; try { const meshConfig = readConfig(); hasMeshes = meshConfig.meshes.length > 0; - primaryMesh = meshConfig.meshes[0]?.slug; } catch {} // Daemon service install — required for MCP integration as of 1.24.0. // The daemon owns the broker WS and feeds the MCP push-pipe via SSE; // skipping it leaves channel push, slash commands, and resources broken. - if (!skipService && hasMeshes && primaryMesh) { + // 1.30.2: install no longer locks the unit to a single mesh; the + // daemon attaches to every joined mesh on boot (1.26.0 multi-mesh + // design). Users who want single-mesh can pass `claudemesh daemon + // install-service --mesh ` explicitly. + if (!skipService && hasMeshes) { try { - installDaemonService(entry, primaryMesh); + installDaemonService(entry); } catch (e) { render.warn( `daemon service install failed: ${e instanceof Error ? e.message : String(e)}`, - "Run `claudemesh daemon install-service --mesh ` to retry.", + "Run `claudemesh daemon install-service` to retry.", ); } } else if (skipService) { @@ -601,7 +603,7 @@ export function runInstall(args: string[] = []): void { * the user knows there's a problem before it shows up as "no messages * arriving." */ -function installDaemonService(binaryEntry: string, meshSlug: string): void { +function installDaemonService(binaryEntry: string): void { const { installService, detectPlatform, @@ -625,17 +627,17 @@ function installDaemonService(binaryEntry: string, meshSlug: string): void { } catch { render.warn( "couldn't resolve a 'claudemesh' binary on PATH; daemon service skipped", - "Install via npm/homebrew, then run `claudemesh daemon install-service --mesh " + meshSlug + "`", + "Install via npm/homebrew, then run `claudemesh daemon install-service`", ); return; } } - const r = installService({ binaryPath: binary, meshSlug }); + const r = installService({ binaryPath: binary }); render.ok(`daemon service installed (${r.platform})`); render.kv([ ["unit", dim(r.unitPath)], - ["mesh", dim(meshSlug)], + ["mesh", dim("(all joined meshes)")], ]); // Boot the unit immediately so MCP has a daemon to attach to on next diff --git a/apps/cli/src/daemon/service-install.ts b/apps/cli/src/daemon/service-install.ts index 2c7cff3..cc1705e 100644 --- a/apps/cli/src/daemon/service-install.ts +++ b/apps/cli/src/daemon/service-install.ts @@ -38,8 +38,13 @@ function isCi(): boolean { export interface InstallArgs { /** Path to the `claudemesh` binary, e.g. /opt/homebrew/bin/claudemesh */ binaryPath: string; - /** Mesh slug to attach to. */ - meshSlug: string; + /** + * Optional mesh slug to lock the daemon to. Omit (the new default) so + * the daemon attaches to every joined mesh — matches the 1.26.0 + * multi-mesh design. Single-mesh lock is preserved for users who + * explicitly want it (testing, CI, host with one mesh). + */ + meshSlug?: string; /** Optional display name. */ displayName?: string; /** Override the auto-detected CI refusal. */ @@ -97,8 +102,9 @@ function installDarwin(args: InstallArgs): InstallResult { `${escapeXml(args.binaryPath)}`, "daemon", "up", - "--mesh", - `${escapeXml(args.meshSlug)}`, + ...(args.meshSlug + ? ["--mesh", `${escapeXml(args.meshSlug)}`] + : []), ...(args.displayName ? ["--name", `${escapeXml(args.displayName)}`] : []), ].join("\n "); @@ -176,7 +182,7 @@ function installLinux(args: InstallArgs): InstallResult { const nodeBin = process.execPath; const execArgs = [ "daemon", "up", - "--mesh", args.meshSlug, + ...(args.meshSlug ? ["--mesh", args.meshSlug] : []), ...(args.displayName ? ["--name", args.displayName] : []), ].map(shellQuote).join(" ");