fix(cli): 1.30.2 — daemon service unit attaches to every joined mesh
claudemesh install was baking --mesh <primary> into the launchd plist / systemd unit, locking the daemon to a single mesh and contradicting 1.26.0's multi-mesh design. users with >1 joined mesh fell off the daemon path on every non-primary verb (cold-WS fallback, peer list returning all meshes because the server-side filter ran against zero attached state, "daemon spawn failed: socket did not appear" from launched sessions in sibling meshes). now: meshSlug is optional in InstallArgs; claudemesh install omits it so the unit runs `claudemesh daemon up` with no flag, which attaches to every joined mesh. `claudemesh daemon install-service --mesh <slug>` is preserved as opt-in for single-mesh hosts and CI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,25 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.30.2 (2026-05-04) — daemon service is multi-mesh by default
|
||||||
|
|
||||||
|
`claudemesh install` was hardcoding `--mesh <primaryMesh>` 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 <slug>` 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
|
## 1.30.1 (2026-05-04) — daemon install upgrade-safe + node-pinned
|
||||||
|
|
||||||
Two install-path fixes that bit on first user upgrade:
|
Two install-path fixes that bit on first user upgrade:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudemesh-cli",
|
"name": "claudemesh-cli",
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"description": "Peer mesh for Claude Code sessions — CLI + MCP server.",
|
"description": "Peer mesh for Claude Code sessions — CLI + MCP server.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"claude-code",
|
"claude-code",
|
||||||
|
|||||||
@@ -190,13 +190,11 @@ async function runInstallService(opts: DaemonOptions): Promise<number> {
|
|||||||
process.stderr.write(`unsupported platform: ${process.platform}\n`);
|
process.stderr.write(`unsupported platform: ${process.platform}\n`);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
if (!opts.mesh) {
|
|
||||||
process.stderr.write(`pass --mesh <slug> 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
|
// Resolve the binary path. Prefer the running argv[0] when it's an
|
||||||
// installed claudemesh binary; fall back to whichever `claudemesh` is
|
// 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] ?? "";
|
let binary = process.argv[1] ?? "";
|
||||||
if (!binary || /\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) {
|
if (!binary || /\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) {
|
||||||
try {
|
try {
|
||||||
@@ -210,8 +208,8 @@ async function runInstallService(opts: DaemonOptions): Promise<number> {
|
|||||||
try {
|
try {
|
||||||
const r = installService({
|
const r = installService({
|
||||||
binaryPath: binary,
|
binaryPath: binary,
|
||||||
meshSlug: opts.mesh,
|
...(opts.mesh ? { meshSlug: opts.mesh } : {}),
|
||||||
displayName: opts.displayName,
|
...(opts.displayName ? { displayName: opts.displayName } : {}),
|
||||||
});
|
});
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
process.stdout.write(JSON.stringify({ ok: true, ...r }) + "\n");
|
process.stdout.write(JSON.stringify({ ok: true, ...r }) + "\n");
|
||||||
|
|||||||
@@ -545,23 +545,25 @@ export function runInstall(args: string[] = []): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let hasMeshes = false;
|
let hasMeshes = false;
|
||||||
let primaryMesh: string | undefined;
|
|
||||||
try {
|
try {
|
||||||
const meshConfig = readConfig();
|
const meshConfig = readConfig();
|
||||||
hasMeshes = meshConfig.meshes.length > 0;
|
hasMeshes = meshConfig.meshes.length > 0;
|
||||||
primaryMesh = meshConfig.meshes[0]?.slug;
|
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
// Daemon service install — required for MCP integration as of 1.24.0.
|
// 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;
|
// The daemon owns the broker WS and feeds the MCP push-pipe via SSE;
|
||||||
// skipping it leaves channel push, slash commands, and resources broken.
|
// 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 <slug>` explicitly.
|
||||||
|
if (!skipService && hasMeshes) {
|
||||||
try {
|
try {
|
||||||
installDaemonService(entry, primaryMesh);
|
installDaemonService(entry);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
render.warn(
|
render.warn(
|
||||||
`daemon service install failed: ${e instanceof Error ? e.message : String(e)}`,
|
`daemon service install failed: ${e instanceof Error ? e.message : String(e)}`,
|
||||||
"Run `claudemesh daemon install-service --mesh <slug>` to retry.",
|
"Run `claudemesh daemon install-service` to retry.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (skipService) {
|
} 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
|
* the user knows there's a problem before it shows up as "no messages
|
||||||
* arriving."
|
* arriving."
|
||||||
*/
|
*/
|
||||||
function installDaemonService(binaryEntry: string, meshSlug: string): void {
|
function installDaemonService(binaryEntry: string): void {
|
||||||
const {
|
const {
|
||||||
installService,
|
installService,
|
||||||
detectPlatform,
|
detectPlatform,
|
||||||
@@ -625,17 +627,17 @@ function installDaemonService(binaryEntry: string, meshSlug: string): void {
|
|||||||
} catch {
|
} catch {
|
||||||
render.warn(
|
render.warn(
|
||||||
"couldn't resolve a 'claudemesh' binary on PATH; daemon service skipped",
|
"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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const r = installService({ binaryPath: binary, meshSlug });
|
const r = installService({ binaryPath: binary });
|
||||||
render.ok(`daemon service installed (${r.platform})`);
|
render.ok(`daemon service installed (${r.platform})`);
|
||||||
render.kv([
|
render.kv([
|
||||||
["unit", dim(r.unitPath)],
|
["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
|
// Boot the unit immediately so MCP has a daemon to attach to on next
|
||||||
|
|||||||
@@ -38,8 +38,13 @@ function isCi(): boolean {
|
|||||||
export interface InstallArgs {
|
export interface InstallArgs {
|
||||||
/** Path to the `claudemesh` binary, e.g. /opt/homebrew/bin/claudemesh */
|
/** Path to the `claudemesh` binary, e.g. /opt/homebrew/bin/claudemesh */
|
||||||
binaryPath: string;
|
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. */
|
/** Optional display name. */
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
/** Override the auto-detected CI refusal. */
|
/** Override the auto-detected CI refusal. */
|
||||||
@@ -97,8 +102,9 @@ function installDarwin(args: InstallArgs): InstallResult {
|
|||||||
`<string>${escapeXml(args.binaryPath)}</string>`,
|
`<string>${escapeXml(args.binaryPath)}</string>`,
|
||||||
"<string>daemon</string>",
|
"<string>daemon</string>",
|
||||||
"<string>up</string>",
|
"<string>up</string>",
|
||||||
"<string>--mesh</string>",
|
...(args.meshSlug
|
||||||
`<string>${escapeXml(args.meshSlug)}</string>`,
|
? ["<string>--mesh</string>", `<string>${escapeXml(args.meshSlug)}</string>`]
|
||||||
|
: []),
|
||||||
...(args.displayName ? ["<string>--name</string>", `<string>${escapeXml(args.displayName)}</string>`] : []),
|
...(args.displayName ? ["<string>--name</string>", `<string>${escapeXml(args.displayName)}</string>`] : []),
|
||||||
].join("\n ");
|
].join("\n ");
|
||||||
|
|
||||||
@@ -176,7 +182,7 @@ function installLinux(args: InstallArgs): InstallResult {
|
|||||||
const nodeBin = process.execPath;
|
const nodeBin = process.execPath;
|
||||||
const execArgs = [
|
const execArgs = [
|
||||||
"daemon", "up",
|
"daemon", "up",
|
||||||
"--mesh", args.meshSlug,
|
...(args.meshSlug ? ["--mesh", args.meshSlug] : []),
|
||||||
...(args.displayName ? ["--name", args.displayName] : []),
|
...(args.displayName ? ["--name", args.displayName] : []),
|
||||||
].map(shellQuote).join(" ");
|
].map(shellQuote).join(" ");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user