FILES omitted the pane list that log --stop relies on and credited
only close with the transcript rescue that clean also performs. The
SECURITY platform list lacked DragonFly. AUTHORS pointed at additional
credits in the script header, which holds none.
Rationale stays; what goes is narration of past revisions (git log
has it), a 'Library mode' section heading with no code under it, a
helper description left above cmd_role after the helper was folded in,
a claim that cmd_close trusts the roster's SOCKET (it never reads it),
and pointers to a README section that is named Security. The FS perms
platform list gains DragonFly.
The already-running check read the roster back only to learn the
socket path cmd_host had computed a few lines earlier. The roster's
presence still gates the probe: a leftover dir without one keeps
falling through to the mkdir lock and its 'party clean' hint.
- tmux_party_setup_server took an unused name parameter; drop it.
- validate_party_name ran two separate case blocks with a length
check between them; merge into one case now that the length check
(which an empty name always passes) runs first.
- roster_read now applies the RR_GROUP fallback to TMUX_PARTY_GROUP
once, so cmd_join, cmd_invite, and cmd_list no longer each repeat
${RR_GROUP:-$TMUX_PARTY_GROUP}.
- socket_path had no caller in party; remove it and its test.
cmd_role's TMUX-socket match and cmd_who's --socket branch each walked
every roster via roster_list to find the one record they already knew
the path to. Read that record directly instead. roster_read still
validates the dir basename and ownership, so a non-party socket falls
through to cmd_role's existing fallback loop.
The __party_guest_$user session name was spelled out in cmd_join,
cmd_leave, cmd_status, cmd_role, and _disconnect_user. Add
guest_session_name to print it consistently. Add ensure_guest_session
for the has-session/new-session snippet duplicated in cmd_join and
cmd_role. Add caller_present_on for the client-or-guest-session OR
duplicated in cmd_leave and cmd_status.
cmd_join and cmd_knock both resolved an optional name argument to a
live party via find_party_by_name/pick_live_party with identical error
handling. Share it in one helper.
log_notify re-implemented the client-fanout loop that .party-notify
(written at host time) already provides. Call that script instead.
Wording changes from "logging this party" to "logging the party" to
match the script's "$user $verb the party" template.
cmd_close and cmd_clean each rescued <party dir>/log to
$HOME/party-<name>-<timestamp>.log before removing the dir, with
close's drain poll as the only difference. Share the mechanism in one
helper and keep each caller's own messages and control flow.
cmd_join, cmd_invite, and cmd_list each inlined the id -nG | tr | grep
pipeline instead of calling the existing user_in_group helper. cmd_list
also re-forked id once per party in its loop; now the caller's groups
are captured once before the loop. Also fold user_in_party_group (one
caller) into require_caller_in_group and drop the wrapper.
list ignored --help and listed parties; leave and status ignored stray
arguments; who silently kept the last of several positionals; log took
a positional name that neither its usage nor the man page documents.
All now follow the parser convention of the other subcommands: help on
-h, exit 2 on an unknown flag or unexpected argument.
The tag was shown whenever a party's group differed from
TMUX_PARTY_GROUP, which falls back to 'party' when unset. Since host
time auto-selects the group, a box gating on 'staff' tagged every
line. Compare the visible parties with each other instead.
The rollback trap was armed on EXIT INT TERM HUP with a body that did
not exit. After a signal the shell ran the cleanup, then resumed
cmd_host and kept configuring the party dir it had just removed.
Keep the cleanup on EXIT and make the signal traps exit with the
conventional 128+n status.
Start already skipped panes the host had piped for other purposes,
but --stop swept every pane, killing pipes party log never owned.
Record the pane ids we pipe in .log-panes inside the per-party dir
and close exactly those on --stop; the file dies with the dir.
Guard HOME for set -u in the rescue paths, surface a failed rescue mv
instead of silently dropping the transcript, and never delete the
party dir after a failed save in either close or clean (close now
refuses to delete and exits nonzero on a failed rescue too, matching
clean, since a message promising the transcript survived followed by
rm -rf was a false assurance). Also stop log --stop from announcing a
stop that never started, and sync README's knock/log/write(1) wording
with the actual behavior.
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.
The remediation-message test relied on the runner not being in group
'party'; on boxes where the runner is (the deployment hosts), host
succeeded and the test failed. Route the child's id(1) through the
suite's stub so the caller is deterministically in no candidate group.
tmux pipe-pane on every pane of the party session, appending into
<party dir>/log (0640, party group) so any attendee can grab a copy.
Start and stop are announced to everyone attached; close rescues a
non-empty transcript to the host's home before removing the dir.
Panes opened later are picked up by re-running party log (a
pane_pipe check keeps already-piped panes single-piped, since tmux's
pipe-pane -o toggles an existing pipe closed rather than skipping it).
Raw output, escapes included. Close polls the transcript for size
stability before rescuing it, since a cross-filesystem mv is
copy+unlink and could otherwise drop bytes still draining from the
pipe-pane writers.
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.
party list already shows invite-only parties; knock closes the loop.
The knocker can't run any tmux command against the server, so the
channel is write(1), the same one invite uses toward guests, except
here delivery failure is surfaced: the ping is the whole feature.
Bare 'party host' now walks staff, users, wheel, party and gates on
the first group the caller belongs to, announcing the pick. Boxes
where people already share a system group need no setup; the curated
'party' group remains the fallback and the explicit --group /
TMUX_PARTY_GROUP paths are unchanged. Caller membership is the only
probe (id -nG, portable everywhere); whether a guest is a member stays
an invite-time warning, where it can actually be answered.
One liveness primitive instead of two. The PID argument was a vestige
(format-checked, never probed, since the kill -0 removal); dropping it
means a roster with a garbage SERVER_PID can no longer mask a live
socket in host/moderation/role resolution.
Punctuation sweep across README, ROADMAP, and the script's comments:
em-dashes become colons, commas, or parentheses; bold list labels take
the colon inside the bold with lowercase text after. Comments only in
the script, no code changes.
The gates in front of destructive operations exist to stop mistakes
(stale rosters, typos, racing hosts); stopping a malicious group
member is a side effect, not the design goal. Written into README
§Security and the man page so future checks are judged by that bar.
ROADMAP.md captures the direction: social features (knock, log,
clean) over further hardening, plus the explicit resist list.
Review flagged the 'access not allowed' substring:
if tmux's denial text drifts while keeping exit 0, unauthorized
connections would classify as ok and the false-joined status/leave
misreporting would silently return. A clean authorized list-clients
writes nothing to stderr (verified 3.3a/3.5a/3.6-SunOS, incl. invited
read-only clients), so rc 0 plus any stderr output now classifies as
unauthorized: no message text consulted. Drift in any direction
degrades to a hidden party, never to a false ok.
README: tmux requirement wording, invite-only list marker, name
charset, --party on moderation rows, PARTY_SOCKET_DIR whitespace note,
read-only join fallback. deploy.sh: drop phantom tests/run-remote.sh
include (file lives under legacy/ only) and fix the ship-list comment.
SERVERS.md and HISTORY.md were refreshed too but stay local-only per
.git/info/exclude ("Local docs — never published").
tmux refuses new-session from read-only clients ('client is
read-only'), so an active join for an -r invitee died under set -eu
with a raw tmux error. Detect the R flag on the caller's ACL line in
the existing preflight and fall back to the mirrored view.
tmux answers non-allowlisted users with 'access not allowed' on stderr
and exit 0 for every command (verified 3.3a/3.5a, two users), so
has-session reports any target as existing. party status claimed
uninvited group members were joined, party leave silently 'succeeded',
and party list printed bogus '0 attendee(s)' rows. New party_conn_state
(ok/unauthorized/dead) classifies by message content; list now shows
'invite-only (ask <host>)'.
A host of two or more parties was told 'pass a name' by commands that
accepted no name flag, making moderation impossible. Mirrors the
--party flag invite already had.
The script was committed mode 100644; a fresh clone could not run
./party, and the bats suite fails with EACCES 126 wherever it invokes
"$PARTY_BIN" directly. Makefile installs masked this via install -m 0755.
tmux session_check_name() silently rewrites '.' to '_', so a dotted
party name desyncs from its tmux session and every -t lookup fails
(verified on tmux 3.3a through 3.6b). Leading-dash names can never be
addressed through the CLI parsers.
tmux-party: share a tmux session with people on the same UNIX host.
Single-file POSIX shell (party) with a filesystem + tmux server-access
trust model. See README.md.