Files
stack/agents/rocko/work/queue-72/build.patch
T
jason.woltjeandClaude Opus 5.5 036afdb686 review(rocko): row 72 round 1 packet, Claude pin tests (#1566)
Rocko's packet agents/rocko/work/queue-72/ (candidate manifest e6531a53,
base a0080da8) with their BUILD-LOG and SESSIONS appends, and Sage's
SESSIONS line for the jarvis review request (comment 27454).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 18:06:07 -05:00

169 lines
7.8 KiB
Diff

diff --git a/agents/rocko/work/queue-72/mutants.sh b/agents/rocko/work/queue-72/mutants.sh
new file mode 100755
index 00000000..ebd767b7
--- /dev/null
+++ b/agents/rocko/work/queue-72/mutants.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+# Row 72 (#1566): rerun the six mutants the row 68 reviewers named against a
+# tree with the candidate tests. Usage: mutants.sh <tree>. Each mutant edits
+# one line of bus-service.sh or adapter.sh, runs the pin tests, and restores
+# the file from git. Prints "killed" or "SURVIVED" per mutant; exit 1 if any
+# mutant survives, a sed doesn't change its file, or the unmutated run fails.
+set -euo pipefail
+TREE="$(cd "${1:?usage: mutants.sh <tree>}" && pwd)"
+cd "$TREE"
+BS=scripts/bus-service.sh
+AD=adapters/claude/adapter.sh
+PATTERN='directory pin|symlink default|pin with a space|pins the claude|MOSAIC_CLAUDE_BIN wins'
+TESTS=(packages/cli/tests/host.test.mjs packages/harness/tests/claude-session.test.mjs)
+run_tests() { node --test --test-name-pattern="$PATTERN" "${TESTS[@]}" >"$1" 2>&1; }
+
+# name | file | sed expression
+MUTANTS=(
+ 'bs-no-f|'"$BS"'|s/\[ -f "\$bin" \] \&\& \[ -x "\$bin" \]/[ -e "$bin" ] \&\& [ -x "$bin" ]/'
+ 'bs-x-only|'"$BS"'|s/\[ -f "\$bin" \] \&\& \[ -x "\$bin" \]/[ -x "$bin" ]/'
+ 'ad-no-f|'"$AD"'|s/\[ -f "\$MOSAIC_CLAUDE_BIN" \] \&\& \[ -x "\$MOSAIC_CLAUDE_BIN" \]/[ -e "$MOSAIC_CLAUDE_BIN" ] \&\& [ -x "$MOSAIC_CLAUDE_BIN" ]/'
+ 'ad-x-only|'"$AD"'|s/\[ -f "\$MOSAIC_CLAUDE_BIN" \] \&\& \[ -x "\$MOSAIC_CLAUDE_BIN" \]/[ -x "$MOSAIC_CLAUDE_BIN" ]/'
+ 'bs-resolve-default|'"$BS"'|s/^ bin="\$(command -v claude || true)"$/ bin="$(command -v claude || true)"; [ -z "$bin" ] || bin="$(readlink -f "$bin")"/'
+ 'ad-unquoted-run|'"$AD"'|s/^"\$CLAUDE" -p /$CLAUDE -p /'
+)
+
+OUT="${OUT:-$TREE/.mutants}"
+mkdir -p "$OUT"
+git diff --quiet -- "$BS" "$AD" || { echo "refusing: $BS or $AD already modified" >&2; exit 1; }
+run_tests "$OUT/baseline.log" || { echo "baseline run failed: $OUT/baseline.log" >&2; exit 1; }
+echo "baseline: pass"
+status=0
+for m in "${MUTANTS[@]}"; do
+ IFS='|' read -r name file expr <<<"$m"
+ sed -i -e "$expr" "$file"
+ if git diff --quiet -- "$file"; then
+ echo "$name: NOT APPLIED (sed matched nothing)"; status=1; continue
+ fi
+ git diff -- "$file" >"$OUT/$name.diff"
+ if run_tests "$OUT/$name.log"; then
+ echo "$name: SURVIVED"; status=1
+ else
+ echo "$name: killed ($(grep -E '^✖' "$OUT/$name.log" | sed 's/ ([0-9.]*ms)$//' | sort -u | tr '\n' ';' ))"
+ fi
+ git checkout -q -- "$file"
+done
+git diff --quiet -- "$BS" "$AD" || { echo "restore failed" >&2; exit 1; }
+exit "$status"
diff --git a/packages/cli/tests/host.test.mjs b/packages/cli/tests/host.test.mjs
index 0a5d6825..13a621ae 100644
--- a/packages/cli/tests/host.test.mjs
+++ b/packages/cli/tests/host.test.mjs
@@ -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");
+});
diff --git a/packages/harness/tests/claude-session.test.mjs b/packages/harness/tests/claude-session.test.mjs
index eb680836..213c811e 100644
--- a/packages/harness/tests/claude-session.test.mjs
+++ b/packages/harness/tests/claude-session.test.mjs
@@ -215,6 +215,50 @@ test("claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute e
}
});
+// Row 72 (#1566): a directory pin exits 2 with the adapter's own message,
+// not 126 at exec; a pin under a directory whose name has a space runs,
+// which needs "$CLAUDE" quoted.
+test("claude adapter: a directory pin exits 2 before it runs; a pin with a space runs", (t) => {
+ const dir = scratch(t);
+ mkdirSync(join(dir, "bin"));
+ const stand = (p, word) => {
+ writeFileSync(p, `#!/bin/sh\necho ${word} > "$WHO_OUT"\necho ok\n`);
+ chmodSync(p, 0o755);
+ return p;
+ };
+ stand(join(dir, "bin", "claude"), "path");
+ for (const f of ["prompt.md", "settings.json", "mcp.json"]) writeFileSync(join(dir, f), "{}");
+ const run = (bin) => spawnSync("/bin/sh", [ADAPTER], {
+ encoding: "utf8",
+ stdio: ["ignore", "pipe", "pipe"],
+ env: {
+ PATH: `${join(dir, "bin")}:/usr/bin:/bin`,
+ WHO_OUT: join(dir, "who"),
+ MOSAIC_SYSTEM_PROMPT_FILE: join(dir, "prompt.md"),
+ MOSAIC_REQUEST: "x",
+ MOSAIC_WORKSPACE: join(dir, "ws"),
+ MOSAIC_SESSION_DIR: join(dir, "session"),
+ MOSAIC_MODEL: "claude-sonnet-5-5",
+ MOSAIC_CLAUDE_SETTINGS: join(dir, "settings.json"),
+ MOSAIC_CLAUDE_MCP_CONFIG: join(dir, "mcp.json"),
+ MOSAIC_CLAUDE_BIN: bin,
+ },
+ });
+ const pinDir = join(dir, "claude-dir");
+ mkdirSync(pinDir);
+ chmodSync(pinDir, 0o755);
+ let r = run(pinDir);
+ assert.equal(r.status, 2, r.stderr);
+ assert.match(r.stderr, /MOSAIC_CLAUDE_BIN is not an executable file/);
+ assert.equal(existsSync(join(dir, "who")), false);
+
+ mkdirSync(join(dir, "Claude Code"));
+ const spaced = stand(join(dir, "Claude Code", "claude"), "spaced");
+ r = run(spaced);
+ assert.equal(r.status, 0, r.stderr);
+ assert.equal(readFileSync(join(dir, "who"), "utf8").trim(), "spaced");
+});
+
test("claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do", { skip }, async (t) => {
const { dir, workspace, env } = await session(t, []);
const slug = realpathSync(workspace).replace(/[/.]/g, "-");