Files
claudemesh/apps/cli/tests/unit/paths-stale-env.test.ts
Alejandro Gutiérrez a2a53ff355 feat(cli,broker): 1.34.14 + 1.34.15 — env-var fallback, peer list scope, kick refuses control-plane
Three follow-ups from the 1.34.x multi-session correctness train,
all backwards-compatible.

1.34.14 — stale CLAUDEMESH_CONFIG_DIR falls back. The launch flow
exposes CLAUDEMESH_CONFIG_DIR=<tmpdir> to its spawned claude; if a
later claudemesh invocation inherited that env (Bash tool inside
Claude Code, tmux update-environment, exported var), the inherited
path pointed at a tmpdir that no longer existed and readConfig()
silently returned empty. paths.ts now memoizes resolution: env unset
→ default; env points at a real dir → trust it; env set but dir gone
→ TTY-only stderr warning with shell-specific unset hint, fall back
to ~/.claudemesh.

1.34.15 — peer list --mesh actually scopes. peers.ts and launch.ts
were calling tryListPeersViaDaemon() with no argument; the daemon's
?mesh= filter (server-side, since 1.26.0) was already correct, the
CLI just wasn't passing the slug. Forwarding fixed in both sites;
send.ts cross-mesh hex-prefix resolution intentionally untouched.

1.34.15 — kick refuses no-op kicks on control-plane. Pre-1.34.15
kicking a daemon's member-WS just closed the socket and triggered
auto-reconnect — a no-op with a misleading "session ended" message.
Broker now skips peers where peerRole === "control-plane" and
surfaces them in a new additive ack field skipped_control_plane;
the CLI reads it and prints a clearer hint pointing at ban / daemon
down. Soft disconnect verb keeps old behavior. PeerConn gains a
peerRole slot populated at both connections.set sites.

Tests: 4 new for paths-stale-env, 5 for kick-control-plane-skip.
CLI 87/87 green; broker 55/55 unit green (integration tests
pre-existing infra failure on this machine).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:59:06 +01:00

58 lines
2.3 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { mkdirSync, rmSync, existsSync } from "node:fs";
import { join } from "node:path";
import { tmpdir, homedir } from "node:os";
/** Each test imports a fresh copy of paths.ts via dynamic import +
* `_resetPathsForTest()` so memoization doesn't leak across cases. */
const TEST_DIR = join(tmpdir(), "claudemesh-paths-test-" + Date.now());
describe("paths CONFIG_DIR resolution", () => {
beforeEach(() => {
delete process.env.CLAUDEMESH_CONFIG_DIR;
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true, force: true });
});
afterEach(() => {
delete process.env.CLAUDEMESH_CONFIG_DIR;
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true, force: true });
});
it("falls back to ~/.claudemesh when env var is unset", async () => {
const mod = await import("~/constants/paths.js");
mod._resetPathsForTest();
expect(mod.PATHS.CONFIG_DIR).toBe(join(homedir(), ".claudemesh"));
});
it("honors CLAUDEMESH_CONFIG_DIR when the dir exists, even without config.json", async () => {
mkdirSync(TEST_DIR, { recursive: true });
process.env.CLAUDEMESH_CONFIG_DIR = TEST_DIR;
const mod = await import("~/constants/paths.js");
mod._resetPathsForTest();
expect(mod.PATHS.CONFIG_DIR).toBe(TEST_DIR);
});
it("falls back to default when env points at a missing dir (stale-tmpdir case)", async () => {
process.env.CLAUDEMESH_CONFIG_DIR = "/var/folders/_nonexistent_claudemesh_dir_xyz123";
const mod = await import("~/constants/paths.js");
mod._resetPathsForTest();
// Suppress the stderr warning to keep test output clean
const stderr = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
try {
expect(mod.PATHS.CONFIG_DIR).toBe(join(homedir(), ".claudemesh"));
} finally {
stderr.mockRestore();
}
});
it("memoizes — second access returns the same path even if env changes mid-process", async () => {
mkdirSync(TEST_DIR, { recursive: true });
process.env.CLAUDEMESH_CONFIG_DIR = TEST_DIR;
const mod = await import("~/constants/paths.js");
mod._resetPathsForTest();
const first = mod.PATHS.CONFIG_DIR;
process.env.CLAUDEMESH_CONFIG_DIR = "/something/else";
expect(mod.PATHS.CONFIG_DIR).toBe(first);
});
});