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]>
169 lines
7.8 KiB
Diff
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, "-");
|