Skip to content

Commit e4400b6

Browse files
committed
fix(smoke): retire fixture daemons before every install-fixture cleanup
Phase 9b's fixture installs spawn ephemeral daemons whose mapped generation backings pin files in the fixture HOME; the cleanup rm then races the daemon's asynchronous drain and loses nondeterministically on Windows (Device or resource busy on a generations backing — the same class the final-phase daemon retirement already fixed for the smoke cache, two rounds green then one red on identical code). The retirement is now a shared helper with a cheap not-running probe, called before every fixture cleanup that follows an install and by the final phase; a daemon that will not retire stays a hard failure. The 9b fixtures also route LOCALAPPDATA under the fixture HOME: their installs staged launcher pairs into the real runner profile, outside the fixture's isolation and the harness's cleanup reach. Verified: full VM smoke green end to end (all 9b subphases, phase 17 through the shared helper). Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
1 parent 0414146 commit e4400b6

1 file changed

Lines changed: 37 additions & 16 deletions

File tree

‎scripts/smoke-test.sh‎

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,33 @@ copy_smoke_binary() {
5555
fi
5656
}
5757

58+
# Retire the shared account daemon (if one is running) and wait until it
59+
# reports not-running. Install/uninstall flows leave an ephemeral daemon
60+
# draining asynchronously whose mapped generation backing and open logs
61+
# block rm on Windows (POSIX rm doesn't care) — so every cleanup of a
62+
# fixture HOME that received an install, and the final cache removal, must
63+
# retire it deterministically first. A daemon that will not retire is a
64+
# failure in its own right, never a tolerated race.
65+
retire_account_daemon() {
66+
local label="$1"
67+
if ! "$BINARY" daemon status >/dev/null 2>&1; then
68+
return 0
69+
fi
70+
"$BINARY" daemon stop >/dev/null 2>&1 || true
71+
local gone=0
72+
for _ in $(seq 1 100); do # bounded ~20s (100 x 0.2s)
73+
if ! "$BINARY" daemon status >/dev/null 2>&1; then gone=1; break; fi
74+
sleep 0.2
75+
done
76+
if [ "$gone" -ne 1 ]; then
77+
echo "FAIL $label: account daemon still active after daemon stop"
78+
exit 1
79+
fi
80+
if [[ "$BINARY" == *.exe ]]; then
81+
sleep 1
82+
fi
83+
}
84+
5885
TMPDIR=$(smoke_mktemp_dir)
5986
DRYRUN_HOME=""
6087
# On MSYS2/Windows, convert POSIX path to native Windows path for the binary
@@ -2569,24 +2596,25 @@ echo "--- Phase 9b: adversarial install/uninstall tests ---"
25692596
# completes without crash and prints "Detected agents:" line.
25702597
EMPTY_HOME=$(smoke_mktemp_dir)
25712598
mkdir -p "$EMPTY_HOME/.local/bin"
2572-
INSTALL_OUT=$(HOME="$EMPTY_HOME" "$BINARY" install -y 2>&1) || true
2599+
INSTALL_OUT=$(HOME="$EMPTY_HOME" LOCALAPPDATA="$EMPTY_HOME/AppData/Local" "$BINARY" install -y 2>&1) || true
25732600
if ! echo "$INSTALL_OUT" | grep -qi 'detected agents'; then
25742601
echo "FAIL 9b-1: install output missing 'Detected agents' line"
25752602
exit 1
25762603
fi
25772604
echo "OK 9b-1: install with minimal agents exits cleanly"
2605+
retire_account_daemon "9b-1-cleanup"
25782606
rm -rf "$EMPTY_HOME"
25792607

25802608
# 9b-2: Install twice (idempotent)
25812609
IDEM_HOME=$(smoke_mktemp_dir)
25822610
mkdir -p "$IDEM_HOME/.claude" "$IDEM_HOME/.local/bin"
25832611
copy_smoke_binary "$IDEM_HOME/.local/bin/codebase-memory-mcp"
2584-
HOME="$IDEM_HOME" "$BINARY" install -y 2>&1 > /dev/null || true
2612+
HOME="$IDEM_HOME" LOCALAPPDATA="$IDEM_HOME/AppData/Local" "$BINARY" install -y 2>&1 > /dev/null || true
25852613
IDEM_INSTALLER="$BINARY"
25862614
if [[ "$BINARY" == *.exe ]]; then
25872615
IDEM_INSTALLER="$IDEM_HOME/.local/bin/codebase-memory-mcp.exe"
25882616
fi
2589-
HOME="$IDEM_HOME" "$IDEM_INSTALLER" install -y 2>&1 > /dev/null || true
2617+
HOME="$IDEM_HOME" LOCALAPPDATA="$IDEM_HOME/AppData/Local" "$IDEM_INSTALLER" install -y 2>&1 > /dev/null || true
25902618
# Count MCP entries — should be exactly 1
25912619
COUNT=$(cat "$IDEM_HOME/.claude.json" 2>/dev/null | python3 -c "
25922620
import json, sys
@@ -2598,13 +2626,15 @@ if [ "$COUNT" != "1" ]; then
25982626
exit 1
25992627
fi
26002628
echo "OK 9b-2: double install is idempotent"
2629+
retire_account_daemon "9b-2-cleanup"
26012630
rm -rf "$IDEM_HOME"
26022631

26032632
# 9b-3: Uninstall without prior install
26042633
CLEAN_HOME=$(smoke_mktemp_dir)
26052634
mkdir -p "$CLEAN_HOME/.claude" "$CLEAN_HOME/.local/bin"
26062635
UNINSTALL_OUT=$(HOME="$CLEAN_HOME" "$BINARY" uninstall -y -n 2>&1) || true
26072636
echo "OK 9b-3: uninstall without install doesn't crash"
2637+
retire_account_daemon "9b-3-cleanup"
26082638
rm -rf "$CLEAN_HOME"
26092639

26102640
# 9b-4: Install over corrupt JSON
@@ -2615,6 +2645,7 @@ echo '{invalid json here' > "$CORRUPT_HOME/.claude.json"
26152645
HOME="$CORRUPT_HOME" "$BINARY" install -y 2>&1 > /dev/null || true
26162646
# Should either fix it or handle gracefully — not crash
26172647
echo "OK 9b-4: install over corrupt JSON doesn't crash"
2648+
retire_account_daemon "9b-4-cleanup"
26182649
rm -rf "$CORRUPT_HOME"
26192650

26202651
# 9b-8: Double uninstall
@@ -2629,6 +2660,7 @@ fi
26292660
HOME="$DBL_HOME" "$DBL_UNINSTALLER" uninstall -y -n 2>&1 > /dev/null || true
26302661
HOME="$DBL_HOME" "$BINARY" uninstall -y -n 2>&1 > /dev/null || true
26312662
echo "OK 9b-8: double uninstall doesn't crash"
2663+
retire_account_daemon "9b-8-cleanup"
26322664
rm -rf "$DBL_HOME"
26332665

26342666
# 9b-9: Non-interactive update without --standard/--ui should fail cleanly (not hang)
@@ -2642,6 +2674,7 @@ if [ "$(uname -s)" != "MINGW64_NT" ] 2>/dev/null; then
26422674
fi
26432675
fi
26442676

2677+
retire_account_daemon "9-cleanup"
26452678
rm -rf "$FAKE_HOME" "$EMPTY_HOME"
26462679

26472680
if [ "$SMOKE_MODE" = "--agent-config-only" ]; then
@@ -3270,19 +3303,7 @@ echo "OK 16: stdio server terminated after stdin closed, no orphan"
32703303
# resource busy". Retire the daemon deterministically through its own
32713304
# lifecycle command and wait (bounded) until it reports not-running, then give
32723305
# Windows one beat for the final handle close.
3273-
"$BINARY" daemon stop >/dev/null 2>&1 || true
3274-
DAEMON_GONE=0
3275-
for _ in $(seq 1 100); do # bounded ~20s (100 × 0.2s)
3276-
if ! "$BINARY" daemon status >/dev/null 2>&1; then DAEMON_GONE=1; break; fi
3277-
sleep 0.2
3278-
done
3279-
if [ "$DAEMON_GONE" -ne 1 ]; then
3280-
echo "FAIL 17: account daemon still active after daemon stop"
3281-
exit 1
3282-
fi
3283-
if [[ "$BINARY" == *.exe ]]; then
3284-
sleep 1
3285-
fi
3306+
retire_account_daemon 17
32863307
echo "OK 17: account daemon retired; smoke cache is deletable"
32873308

32883309
echo ""

0 commit comments

Comments
 (0)