feat: party clean removes the caller's own crash leftovers

Turns the manual rm -rf recovery from host's EEXIST message into a
verb, behind the same gates cmd_close uses: own-basename glob, no
symlinks, ownership check, and a dead socket. Live parties are skipped
with a pointer to party close.
This commit is contained in:
veg 2026-07-11 12:32:39 +00:00
parent 6799a57405
commit a011e4b03c
6 changed files with 133 additions and 9 deletions

81
tests/85-clean.bats Normal file
View file

@ -0,0 +1,81 @@
#!/usr/bin/env bats
#
# party clean: remove the caller's own dead party dirs. Mistake-proofing
# gates mirror cmd_close: own-basename glob, no symlinks, [ -O ], and a
# dead socket. Liveness is faked through PARTY_TMUX stubs so no real
# server or group membership is needed.
load 'helpers'
setup() {
setup_party_sandbox
load_party_lib
}
teardown() { teardown_party_sandbox; }
@test "clean removes a dead leftover dir" {
ensure_party_dir "$USER" ghost
run "$PARTY_BIN" clean
[ "$status" -eq 0 ]
[[ "$output" == *"removed"*"ghost"* ]]
[ ! -d "$PARTY_SOCKET_DIR/party-$USER:ghost.d" ]
}
@test "clean removes a crash-before-bind dir (no sock at all)" {
mkdir "$PARTY_SOCKET_DIR/party-$USER:stillborn.d"
run "$PARTY_BIN" clean
[ "$status" -eq 0 ]
[ ! -d "$PARTY_SOCKET_DIR/party-$USER:stillborn.d" ]
}
@test "clean skips a live party and says to use close" {
cat > "$PARTY_TMP/tmux-ok" <<'EOF'
#!/bin/sh
exit 0
EOF
chmod +x "$PARTY_TMP/tmux-ok"
export PARTY_TMUX="$PARTY_TMP/tmux-ok"
ensure_party_dir "$USER" running
run "$PARTY_BIN" clean
[ "$status" -eq 0 ]
[[ "$output" == *"skipping"*"running"* ]]
[[ "$output" == *"party close"* ]]
[ -d "$PARTY_SOCKET_DIR/party-$USER:running.d" ]
}
@test "clean never follows a planted symlink" {
mkdir "$PARTY_TMP/target"
: > "$PARTY_TMP/target/precious"
ln -s "$PARTY_TMP/target" "$PARTY_SOCKET_DIR/party-$USER:lnk.d"
run "$PARTY_BIN" clean
[ "$status" -eq 0 ]
[ -e "$PARTY_TMP/target/precious" ]
[ -L "$PARTY_SOCKET_DIR/party-$USER:lnk.d" ]
}
@test "clean ignores other users' dirs" {
ensure_party_dir nobody theirparty
run "$PARTY_BIN" clean
[ "$status" -eq 0 ]
[ -d "$PARTY_SOCKET_DIR/party-nobody:theirparty.d" ]
}
@test "clean with nothing to do says so" {
run "$PARTY_BIN" clean
[ "$status" -eq 0 ]
[[ "$output" == *"nothing to clean"* ]]
}
@test "clean rejects arguments" {
run "$PARTY_BIN" clean extra
[ "$status" -eq 2 ]
}
@test "host's EEXIST message recommends party clean" {
ensure_party_dir "$USER" blocked
export TMUX_PARTY_GROUP="$(id -gn)"
run "$PARTY_BIN" host blocked
[ "$status" -eq 1 ]
[[ "$output" == *"party clean"* ]]
}

View file

@ -564,7 +564,7 @@ STUB
# deliberately do NOT auto-clean: the same code path triggers on a
# concurrent in-flight host attempt for the same name, and racing
# rm -rf calls would wipe each other's fresh dirs.
# Recovery is a manual `rm -rf` per the error message.
# Recovery is `party clean` per the error message.
party_dir="$PARTY_SOCKET_DIR/party-$USER:resurrect.d"
mkdir -p "$party_dir"
: > "$party_dir/sock"
@ -572,7 +572,7 @@ STUB
run "$PARTY_BIN" host resurrect
[ "$status" -ne 0 ]
[[ "$output" == *"already exists"* ]]
[[ "$output" == *"rm -rf $party_dir"* ]]
[[ "$output" == *"party clean"* ]]
# The leftover dir is untouched — proves we did not race-clean.
[ -d "$party_dir" ]