test(cli, harness): Claude pin tests, a directory pin, a symlink default, a pin with a space (#1566, row 72)

One test in packages/cli/tests/host.test.mjs: a directory given with
--claude is refused, and a symlink default stays the symlink path. One
in packages/harness/tests/claude-session.test.mjs: a directory pin exits
2 before claude runs, and a pin under a directory with a space runs.
agents/rocko/work/queue-72/mutants.sh (committed with the packet in
036afdb6) reruns the six row 68 mutants: all killed on the candidate,
all surviving on the base. Accepted deviation: bash's command -v never
returns a directory, so a directory named claude on PATH takes the
no-claude branch, which the test asserts.

Author: Rocko. Candidate manifest e6531a5311f17ea147700e417dfdbd766a9c38acf3c3b44755267c285b78e006
(base a0080da8, no drift on the candidate paths, bus-service.sh or
adapter.sh). Approved by Filbert, comment 27460 on #1566; their N1 is
row 78, #1572.

Sage gate rerun on a worktree of df676fa1 with the patch: manifest OK;
cli 113/113, harness 58/58, 0 skipped; test-auth 15, conductor 17,
config 24, discord 66, extension-package 18, foundation 44, queue 27,
release 14, task 98, all 0 failed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-11 18:17:03 -05:00
co-authored by Claude Opus 5.5
parent f6b9ea4e3f
commit d436fe8bdc
2 changed files with 96 additions and 0 deletions
+52
View File
@@ -559,3 +559,55 @@ test("bus-service.sh pins the claude binary, refuses one the unit can't carry, a
assert.match(none.stdout, /^# MOSAIC_CLAUDE_BIN unset: no claude on PATH at install time$/m);
assert.doesNotMatch(none.stdout, /@CLAUDE_ENV@/);
});
// Row 72 (#1566): mutants row 68's tests left alive. A directory is -x, so
// only the -f check keeps a directory pin out of the unit; the default pin
// keeps a symlink as given, so a claude that updates through it is followed.
test("bus-service.sh refuses a directory pin and keeps a symlink default unresolved", (t) => {
const dir = tmp(t);
const script = join(REPO, "scripts", "bus-service.sh");
const pinned = (out) => out.match(/^Environment=MOSAIC_CLAUDE_BIN=(.*)$/m)?.[1];
const tools = join(dir, "tools");
mkdirSync(tools);
symlinkSync(process.execPath, join(tools, "node"));
for (const tool of ["dirname", "sed", "readlink"]) {
const at = ["/usr/bin", "/bin"].map((d) => join(d, tool)).find((p) => existsSync(p));
symlinkSync(at, join(tools, tool));
}
const bash = ["/usr/bin/bash", "/bin/bash"].find((p) => existsSync(p));
const run = (args, bin) => spawnSync(bash, [script, ...args], { encoding: "utf8", env: { PATH: `${bin}:${tools}`, HOME: dir } });
// --claude DIR: a directory passes -x and fails -f.
const pinDir = join(dir, "claude-dir");
mkdirSync(pinDir);
chmodSync(pinDir, 0o755);
let r = run(["render", "--claude", pinDir], join(dir, "empty"));
assert.equal(r.status, 1);
assert.match(r.stderr, /not an executable file/);
assert.equal(r.stdout, "");
// A directory named claude on PATH: bash's `command -v` never returns a
// directory, so the default finds no claude and pins nothing. It never
// pins the directory.
const dirBin = join(dir, "dir-bin");
mkdirSync(join(dirBin, "claude"), { recursive: true });
chmodSync(join(dirBin, "claude"), 0o755);
r = run(["render"], dirBin);
assert.equal(r.status, 0, r.stderr);
assert.match(r.stderr, /no claude on PATH; MOSAIC_CLAUDE_BIN left unset/);
assert.equal(pinned(r.stdout), undefined);
assert.ok(!r.stdout.includes(join(dirBin, "claude")));
// A symlink named claude on PATH: the pin is the link, not its target.
const real = join(dir, "versions", "claude-9.9.9");
mkdirSync(dirname(real));
writeFileSync(real, '#!/bin/sh\necho "9.9.9 (Claude Code)"\n');
chmodSync(real, 0o755);
const linkBin = join(dir, "link-bin");
mkdirSync(linkBin);
symlinkSync(real, join(linkBin, "claude"));
r = run(["render"], linkBin);
assert.equal(r.status, 0, r.stderr);
assert.equal(pinned(r.stdout), join(linkBin, "claude"));
assert.ok(!r.stdout.includes(real), "the target never reaches the unit");
});