@@ -82,6 +82,21 @@ retire_account_daemon() {
8282 fi
8383}
8484
85+ # Tolerate ERRORS, never CRASHES: the adversarial phases assert "doesn't
86+ # crash", and a bare `|| true` discards exactly the crash exit the claim is
87+ # about. rc >= 128 means killed by signal / fatal status (SIGSEGV=139,
88+ # SIGABRT=134 — ASan aborts land here too).
89+ run_no_crash () {
90+ local label=" $1 " ; shift
91+ local rc=0
92+ " $@ " > /dev/null 2>&1 || rc=$?
93+ if [ " $rc " -ge 128 ]; then
94+ echo " FAIL $label : crashed (rc=$rc )"
95+ exit 1
96+ fi
97+ return 0
98+ }
99+
85100TMPDIR=$( smoke_mktemp_dir)
86101DRYRUN_HOME=" "
87102# On MSYS2/Windows, convert POSIX path to native Windows path for the binary
@@ -2596,7 +2611,12 @@ echo "--- Phase 9b: adversarial install/uninstall tests ---"
25962611# completes without crash and prints "Detected agents:" line.
25972612EMPTY_HOME=$( smoke_mktemp_dir)
25982613mkdir -p " $EMPTY_HOME /.local/bin"
2599- INSTALL_OUT=$( HOME=" $EMPTY_HOME " LOCALAPPDATA=" $EMPTY_HOME /AppData/Local" " $BINARY " install -y 2>&1 ) || true
2614+ INSTALL_RC=0
2615+ INSTALL_OUT=$( HOME=" $EMPTY_HOME " LOCALAPPDATA=" $EMPTY_HOME /AppData/Local" " $BINARY " install -y 2>&1 ) || INSTALL_RC=$?
2616+ if [ " $INSTALL_RC " -ge 128 ]; then
2617+ echo " FAIL 9b-1: install crashed (rc=$INSTALL_RC )"
2618+ exit 1
2619+ fi
26002620if ! echo " $INSTALL_OUT " | grep -qi ' detected agents' ; then
26012621 echo " FAIL 9b-1: install output missing 'Detected agents' line"
26022622 exit 1
@@ -2609,12 +2629,12 @@ rm -rf "$EMPTY_HOME"
26092629IDEM_HOME=$( smoke_mktemp_dir)
26102630mkdir -p " $IDEM_HOME /.claude" " $IDEM_HOME /.local/bin"
26112631copy_smoke_binary " $IDEM_HOME /.local/bin/codebase-memory-mcp"
2612- HOME=" $IDEM_HOME " LOCALAPPDATA=" $IDEM_HOME /AppData/Local" " $BINARY " install -y 2>&1 > /dev/null || true
2632+ run_no_crash 9b-2 env HOME=" $IDEM_HOME " LOCALAPPDATA=" $IDEM_HOME /AppData/Local" " $BINARY " install -y
26132633IDEM_INSTALLER=" $BINARY "
26142634if [[ " $BINARY " == * .exe ]]; then
26152635 IDEM_INSTALLER=" $IDEM_HOME /.local/bin/codebase-memory-mcp.exe"
26162636fi
2617- HOME=" $IDEM_HOME " LOCALAPPDATA=" $IDEM_HOME /AppData/Local" " $IDEM_INSTALLER " install -y 2>&1 > /dev/null || true
2637+ run_no_crash 9b-2-second env HOME=" $IDEM_HOME " LOCALAPPDATA=" $IDEM_HOME /AppData/Local" " $IDEM_INSTALLER " install -y
26182638# Count MCP entries — should be exactly 1
26192639COUNT=$( cat " $IDEM_HOME /.claude.json" 2> /dev/null | python3 -c "
26202640import json, sys
@@ -2632,7 +2652,12 @@ rm -rf "$IDEM_HOME"
26322652# 9b-3: Uninstall without prior install
26332653CLEAN_HOME=$( smoke_mktemp_dir)
26342654mkdir -p " $CLEAN_HOME /.claude" " $CLEAN_HOME /.local/bin"
2635- UNINSTALL_OUT=$( HOME=" $CLEAN_HOME " " $BINARY " uninstall -y -n 2>&1 ) || true
2655+ UNINSTALL_RC=0
2656+ UNINSTALL_OUT=$( HOME=" $CLEAN_HOME " " $BINARY " uninstall -y -n 2>&1 ) || UNINSTALL_RC=$?
2657+ if [ " $UNINSTALL_RC " -ge 128 ]; then
2658+ echo " FAIL 9b-3: uninstall crashed (rc=$UNINSTALL_RC )"
2659+ exit 1
2660+ fi
26362661echo " OK 9b-3: uninstall without install doesn't crash"
26372662retire_account_daemon " 9b-3-cleanup"
26382663rm -rf " $CLEAN_HOME "
@@ -2642,7 +2667,7 @@ CORRUPT_HOME=$(smoke_mktemp_dir)
26422667mkdir -p " $CORRUPT_HOME /.claude" " $CORRUPT_HOME /.local/bin"
26432668copy_smoke_binary " $CORRUPT_HOME /.local/bin/codebase-memory-mcp"
26442669echo ' {invalid json here' > " $CORRUPT_HOME /.claude.json"
2645- HOME=" $CORRUPT_HOME " " $BINARY " install -y 2>&1 > /dev/null || true
2670+ run_no_crash 9b-4 env HOME=" $CORRUPT_HOME " " $BINARY " install -y
26462671# Should either fix it or handle gracefully — not crash
26472672echo " OK 9b-4: install over corrupt JSON doesn't crash"
26482673retire_account_daemon " 9b-4-cleanup"
@@ -2652,13 +2677,13 @@ rm -rf "$CORRUPT_HOME"
26522677DBL_HOME=$( smoke_mktemp_dir)
26532678mkdir -p " $DBL_HOME /.claude" " $DBL_HOME /.local/bin"
26542679copy_smoke_binary " $DBL_HOME /.local/bin/codebase-memory-mcp"
2655- HOME=" $DBL_HOME " " $BINARY " install -y 2>&1 > /dev/null || true
2680+ run_no_crash 9b-8-install env HOME=" $DBL_HOME " " $BINARY " install -y
26562681DBL_UNINSTALLER=" $BINARY "
26572682if [[ " $BINARY " == * .exe ]]; then
26582683 DBL_UNINSTALLER=" $DBL_HOME /.local/bin/codebase-memory-mcp.exe"
26592684fi
2660- HOME=" $DBL_HOME " " $DBL_UNINSTALLER " uninstall -y -n 2>&1 > /dev/null || true
2661- HOME=" $DBL_HOME " " $BINARY " uninstall -y -n 2>&1 > /dev/null || true
2685+ run_no_crash 9b-8-first env HOME=" $DBL_HOME " " $DBL_UNINSTALLER " uninstall -y -n
2686+ run_no_crash 9b-8-second env HOME=" $DBL_HOME " " $BINARY " uninstall -y -n
26622687echo " OK 9b-8: double uninstall doesn't crash"
26632688retire_account_daemon " 9b-8-cleanup"
26642689rm -rf " $DBL_HOME "
@@ -2783,7 +2808,8 @@ echo "=== Phase 11: process kill E2E ==="
27832808# Start MCP server in background
27842809MCP_KILL_INPUT=$( smoke_mktemp_file)
27852810echo ' {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"kill-test","version":"1.0"}}}' > " $MCP_KILL_INPUT "
2786- " $BINARY " < " $MCP_KILL_INPUT " > /dev/null 2>&1 &
2811+ KILL_OUT=$( smoke_mktemp_file)
2812+ " $BINARY " < " $MCP_KILL_INPUT " > " $KILL_OUT " 2>&1 &
27872813KILL_PID=$!
27882814sleep 1
27892815
@@ -2798,8 +2824,20 @@ if kill -0 "$KILL_PID" 2>/dev/null; then
27982824 fi
27992825 echo " OK 11c-d: process killed successfully"
28002826else
2801- echo " OK 11: MCP server already exited (clean shutdown on EOF)"
2827+ # The server ended before the probe: distinguish clean shutdown-on-EOF
2828+ # from an instant crash — the old branch treated both as OK with the
2829+ # output discarded, so a startup segfault read as a clean pass on any
2830+ # fast machine. A clean run must have exited 0 AND produced the
2831+ # initialize response.
2832+ KILL_RC=0
2833+ wait " $KILL_PID " 2> /dev/null || KILL_RC=$?
2834+ if [ " $KILL_RC " -ge 128 ] || ! grep -q ' "jsonrpc"' " $KILL_OUT " ; then
2835+ echo " FAIL 11: server exited before probe with rc=$KILL_RC and $( wc -c < " $KILL_OUT " ) bytes of output — crash, not clean EOF shutdown"
2836+ exit 1
2837+ fi
2838+ echo " OK 11: MCP server already exited (clean shutdown on EOF, initialize answered)"
28022839fi
2840+ rm -f " $KILL_OUT "
28032841
28042842rm -f " $MCP_KILL_INPUT "
28052843
@@ -3228,13 +3266,24 @@ fi
32283266echo " "
32293267echo " === Phase 15: UI HTTP server ==="
32303268
3231- UI_PORT=19876
3269+ # A kernel-assigned free port instead of a fixed one: a squatter on a fixed
3270+ # port made the whole phase read as SKIP. The tiny TOCTOU window between
3271+ # probe and bind is the residual risk, not the common case.
3272+ UI_PORT=$( python3 -c ' import socket; s = socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()' )
32323273UI_INPUT=$( smoke_mktemp_file)
32333274" $BINARY " --port " $UI_PORT " < " $UI_INPUT " > /dev/null 2>&1 &
32343275UI_PID=$!
3235- sleep 1
3276+ # Readiness poll instead of a fixed sleep: SKIP is legitimate ONLY when the
3277+ # process exited (the documented no-embedded-assets case); a slow start on a
3278+ # loaded runner must not masquerade as it.
3279+ UI_READY=0
3280+ for _ in $( seq 1 100) ; do
3281+ if ! kill -0 " $UI_PID " 2> /dev/null; then break ; fi
3282+ if curl -sf " http://127.0.0.1:$UI_PORT /" -o /dev/null 2> /dev/null; then UI_READY=1; break ; fi
3283+ sleep 0.1
3284+ done
32363285
3237- if kill -0 " $UI_PID " 2> /dev/null; then
3286+ if [ " $UI_READY " -eq 1 ] || kill -0 " $UI_PID " 2> /dev/null; then
32383287 # 15a: GET / returns 200 with HTML content
32393288 UI_BODY=$( curl -sf " http://127.0.0.1:$UI_PORT /" 2> /dev/null || echo " " )
32403289 if echo " $UI_BODY " | grep -qi " <html" ; then
@@ -3258,6 +3307,8 @@ if kill -0 "$UI_PID" 2>/dev/null; then
32583307 echo " SKIP 15b: /rpc not reachable"
32593308 else
32603309 echo " FAIL 15b: /rpc did not return JSON-RPC"
3310+ kill " $UI_PID " 2> /dev/null || true
3311+ exit 1
32613312 fi
32623313
32633314 kill " $UI_PID " 2> /dev/null || true
0 commit comments