From 6839b2035ace8a689ddb7e6ce91c318aa7b19dce Mon Sep 17 00:00:00 2001 From: Zeeshan Ahmed Date: Wed, 2 Sep 2026 19:23:23 +0300 Subject: [PATCH] fix(tests): build fm-muse-harness's renamed process with a symlink The detection cases built their renamed muse-bin- ancestor by copying the system bash. macOS refuses to exec such a copy: it keeps Apple's platform signature at a non-system path, so the exec is denied and the process is SIGKILLed before it runs (verified macOS 26.5.1, arm64: `cp /bin/bash x; x -c 'echo ran'` exits 137, while the same copy re-signed ad hoc with `codesign -f -s -` runs). Build the renamed executable with a symlink instead, which execs the real interpreter while the kernel takes the process name from the launch path, presenting the intended name to both `ps -o comm=` on macOS and /proc//comm on Linux. Add a guard that fails loudly if that construction ever stops naming the process, so a negative detection case cannot pass while silently checking nothing. Verified failing on unpatched upstream main (8988af2) on macOS 26.5.1/arm64 with the same symptom (empty detection result); passes with this fix. --- tests/fm-muse-harness.test.sh | 53 +++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/tests/fm-muse-harness.test.sh b/tests/fm-muse-harness.test.sh index 83a0747458b..da94f3d7a96 100755 --- a/tests/fm-muse-harness.test.sh +++ b/tests/fm-muse-harness.test.sh @@ -64,6 +64,51 @@ write_session_log() { printf '%s\n' "$path" } +# --- real renamed processes ------------------------------------------------- + +# The detection cases need a REAL running process carrying the name under +# test, because bin/fm-harness.sh reads `ps -o comm=` off the live parent +# chain. A copy of the system bash supplied that until macOS stopped executing +# one: a copy of an Apple platform binary keeps Apple's signature at a +# non-system path, the exec is refused, and the process is SIGKILLed before it +# runs (verified macOS 26.5.1, arm64: `cp /bin/bash x; x -c 'echo ran'` exits +# 137, while the same copy re-signed ad hoc with `codesign -f -s -` runs). +# +# A symlink execs the real interpreter while the kernel takes the process name +# from the path used to launch it, so it presents exactly the name under test: +# macOS reports the symlink path from `ps -o comm=`, and Linux reports its +# basename in /proc//comm, truncated to the kernel's 15-byte limit exactly +# as a copy was. Ad-hoc re-signing was rejected as the repair because it would +# make a portable test depend on a macOS-only signing toolchain. +named_executable() { # + ln -sf "$(command -v bash)" "$1/$2" +} + +# The command substitution around the probe is load-bearing for the same +# reason it is in test_detects_versioned_process_ancestor below. +presented_process_name() { # -> basename of the name its process presents + local out + # The probe must stay single-quoted: $$ has to expand in the launched shell, + # not in this one. + # shellcheck disable=SC2016 + out=$("$1" -c 'r=$(ps -o comm= -p $$); printf "%s" "$r"' 2>/dev/null) || return 1 + basename -- "$out" +} + +# Guard against a silently vacuous case: if the construction above ever stops +# naming the process, a negative assertion would pass while checking nothing. +# Linux truncates the name to 15 bytes, so a prefix is the honest match. +assert_process_presents_name() { # + local observed + observed=$(presented_process_name "$1") \ + || fail "could not launch the renamed executable '$2' to read its process name" + [ -n "$observed" ] || fail "renamed executable '$2' presented an empty process name" + case "$2" in + "$observed"*) ;; + *) fail "renamed executable '$2' presented process name '$observed'; the construction no longer names the process, so every case built on it is vacuous" ;; + esac +} + # --- spawn scaffolding ------------------------------------------------------ make_spawn_fakebin() { @@ -104,7 +149,7 @@ esac exit 0 SH chmod +x "$fakebin/tmux" - cp "$(command -v bash)" "$fakebin/muse-bin-test-version" + named_executable "$fakebin" muse-bin-test-version cat > "$fakebin/muse" <<'SH' #!/usr/bin/env bash set -u @@ -174,7 +219,8 @@ test_detects_versioned_process_ancestor() { dir="$TMP_ROOT/detect" mkdir -p "$dir" for bin in muse-bin-0.1.0-R708.1 muse-bin-9.9.9-RZZZ.9 muse; do - cp "$(command -v bash)" "$dir/$bin" + named_executable "$dir" "$bin" + assert_process_presents_name "$dir/$bin" "$bin" out=$(env -u CLAUDECODE -u PI_CODING_AGENT -u FM_PI_HARNESS -u GROK_AGENT \ -u CURSOR_AGENT -u CURSOR_INVOKED_AS \ "$dir/$bin" -c "r=\$(\"$HARNESS\"); printf '%s' \"\$r\"") @@ -190,7 +236,8 @@ test_detection_is_anchored() { dir="$TMP_ROOT/detect-neg" mkdir -p "$dir" for bin in musescore amuse notmuse-bin muse-binary muse-bind; do - cp "$(command -v bash)" "$dir/$bin" + named_executable "$dir" "$bin" + assert_process_presents_name "$dir/$bin" "$bin" out=$(env -u CLAUDECODE -u PI_CODING_AGENT -u FM_PI_HARNESS -u GROK_AGENT \ -u CURSOR_AGENT -u CURSOR_INVOKED_AS \ "$dir/$bin" -c "r=\$(\"$HARNESS\"); printf '%s' \"\$r\"")