From 8d10e62779cb4c8c530bd832b938d1afa5e6861d Mon Sep 17 00:00:00 2001 From: veg Date: Sat, 11 Jul 2026 13:13:05 +0000 Subject: [PATCH] feat: party clean rescues an orphaned transcript before deleting A server crash mid-logged-party is the one case close's rescue can never have run, and host's EEXIST message funnels the host straight into clean. Deleting the night's log there is exactly the accident class the close-time rescue exists for, so clean applies the same rescue: no quiescence poll needed, the liveness gate just proved the writers are gone. --- README.md | 2 +- party | 15 +++++++++++++++ party.1 | 3 +++ tests/85-clean.bats | 12 ++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a58b8b..ab587a4 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ Three honest caveats, with the full detail in `man party`: | `party who [--short]` | Show invited and attached users for the current party. | | `party status` | Show the caller's own state: hosting, attached, or idle. | | `party close` | Tear down the party server and its roster entry. Host-only. | -| `party clean` | Remove the caller's own dead party dirs: crash leftovers whose tmux server is gone. | +| `party clean` | Remove the caller's own dead party dirs: crash leftovers whose tmux server is gone. Rescues a non-empty transcript first. | | `party log [--stop]` | Record the party into its private dir via `tmux pipe-pane` (host only), group-readable. `--stop` ends it. | | `party join [name] [--passive]` | Join a party. Auto-attaches when one is running; picker otherwise. `--passive` attaches read-only to the host's view (watcher mode). Read-only invitees always join passive. | | `party leave` | Detach and clean up the per-guest session. | diff --git a/party b/party index ada8c15..6339573 100755 --- a/party +++ b/party @@ -1005,6 +1005,21 @@ EOF echo "party clean: skipping $d (server is live; use party close)." >&2 continue fi + + # A crash-orphaned transcript is the same "lost the night's log" + # accident cmd_close's rescue exists for; the server here is + # already confirmed dead by the liveness check above, so unlike + # close there's no pipe-pane `cat` still draining and no + # quiescence poll is needed before moving it. + if [ -s "$d/log" ]; then + _cn_name=${d##*"party-$USER:"} + _cn_name=${_cn_name%.d} + _cn_dest="$HOME/party-$_cn_name-$(date -u '+%Y%m%d-%H%M%S').log" + if mv "$d/log" "$_cn_dest" 2>/dev/null; then + echo "party clean: transcript saved to $_cn_dest" + fi + fi + rm -rf "$d" echo "removed $d" removed=$((removed+1)) diff --git a/party.1 b/party.1 index 61b3afd..96cd041 100644 --- a/party.1 +++ b/party.1 @@ -146,6 +146,9 @@ Host-only. Remove the caller's own dead party directories: crash leftovers whose tmux server no longer answers. Never touches live parties, other users' directories, or symlinks. +A non-empty transcript left by +.Cm log +is saved to the caller's home directory first. .It Cm log Op Fl -stop Op Fl -party Ar name Record the party into .Pa log diff --git a/tests/85-clean.bats b/tests/85-clean.bats index a4fe68f..d326843 100644 --- a/tests/85-clean.bats +++ b/tests/85-clean.bats @@ -22,6 +22,18 @@ teardown() { teardown_party_sandbox; } [ ! -d "$PARTY_SOCKET_DIR/party-$USER:ghost.d" ] } +@test "clean rescues a non-empty transcript before removing the dir" { + export HOME="$PARTY_TMP/home" + mkdir -p "$HOME" + ensure_party_dir "$USER" crashed + printf 'the night\n' > "$PARTY_SOCKET_DIR/party-$USER:crashed.d/log" + run "$PARTY_BIN" clean + [ "$status" -eq 0 ] + [[ "$output" == *"transcript saved to"* ]] + [ ! -d "$PARTY_SOCKET_DIR/party-$USER:crashed.d" ] + grep -q "the night" "$HOME"/party-crashed-*.log +} + @test "clean removes a crash-before-bind dir (no sock at all)" { mkdir "$PARTY_SOCKET_DIR/party-$USER:stillborn.d" run "$PARTY_BIN" clean