PARTY(1)

NAME
party - share a tmux session with the people you already work with

SYNOPSIS
party host [name] [--group group]
party close [name]
party clean
party log [--stop] [--party name]
party join [name] [--passive]
party leave
party knock [name]
party invite user [-r] [--party name]
party voice user [--party name]
party mute user [--party name]
party kick user [--party name]
party detach user [--party name]
party role [active | passive | switch]
party list
party who [--short] [--party name]
party status

DESCRIPTION
party hosts a live tmux(1) session for other users on the same UNIX host, or joins one someone else is hosting, with the ergonomics of ‘screen -x’ and none of the copy/paste friction. Use it to pair-program, mentor, debug an outage together, or demo a workflow live: same terminal, no screen-sharing software, no intermediary servers.

Each party runs on its own dedicated tmux(1) server, distinct from the host's personal tmux(1). This keeps tmux(1) server-access scoped: inviting someone to your party does not expose your other sessions. Two gates protect each party. The filesystem gate is universal across every supported platform: the per-party directory is chgrp(1)'d to TMUX_PARTY_GROUP at mode 0750 so only group members can traverse into it; the socket itself is ‘g+rw’. The auth gate is tmux(1) server-access: a user who passes the FS gate but is not on the allowlist is rejected at the protocol layer. Both gates must pass.

party is designed for small, mutually trusted groups: a hacklab, a tech team, a circle of friends, not strangers across the internet. The auth gate is authoritative; the FS gate is defense in depth.

SUBCOMMANDS

host [name [--group group]]
    Spawn a dedicated party tmux(1) server. With no name, a random one is generated. Names may use letters, digits, ‘_’ and ‘-’, up to 63 characters. Dots are not accepted: tmux(1) silently rewrites ‘’. in session names, which would desync the party name from its session. Only the host is on the allowlist; invite others explicitly with invite. Group precedence: --group flag > TMUX_PARTY_GROUP > auto-selection, which walks ‘staff’, ‘users’, ‘wheel’, ‘party’ and gates on the first the host belongs to, announcing the pick.

close [name]
    Tear down the party server and remove its per-party directory. With no name, closes the only party the caller hosts. Host-only.

clean
    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.

log [--stop [--party name]]
    Record the party into log inside the per-party directory (host only), via tmux(1) pipe-pane. The file is group-readable so any attendee can grab a copy. Starting and stopping is announced to everyone attached. Panes opened after logging starts are not captured until log is re-run; output is raw (ANSI escapes included, read with ‘less -R’). --stop ends recording. close rescues a non-empty transcript to the host's home directory before removing the party directory.

join [name [--passive]]
    Join a party. With no name and exactly one party visible, auto-attaches; otherwise presents a numbered picker. By default joins in active mode (your own session group with an independent window cursor). --passive attaches read-only to the host's session (mirrored view). The read-only boundary is set at attach time and is not toggled by later role changes. An invitee added with invite -r is read-only and always joins passive: tmux(1) refuses session creation from read-only clients, so an active join is not possible.

leave
    Detach from the current party and clean up the per-guest session.

knock [name]
    Ask the host of an invite-only party for an invite. Sends a write(1) message to the host naming the caller and the exact invite command. Works for parties list shows as invite-only: the caller shares the party's group but is not on its allowlist. Fails when the host has no tty or has messages disabled with mesg(1).

invite user [-r [--party name]]
    Add user to the allowlist as read/write. -r or --read-only invites as a watcher. Best-effort write(1) ping is sent if available. --party name selects the party when you host more than one. Host-only.

voice user [--party name]
    Promote user to read/write. Alias: rw. --party name selects the party when you host more than one. Host-only.

mute user [--party name]
    Demote user to read-only. Alias: ro. --party name selects the party when you host more than one. Host-only.

kick user [--party name]
    Revoke user's invite, disconnect their client, and kill their guest session. --party name selects the party when you host more than one. Host-only.

detach user [--party name]
    Disconnect user's client and kill their guest session, but keep them on the allowlist. --party name selects the party when you host more than one. Host-only.

role [active | passive | switch]
    Flip your clients between the guest session and the host session. With no argument, prints the current role for each of your clients. Session-level only: the read-only boundary set at attach time (see join --passive) is not toggled by role changes ( tmux(1) switch-client has no read-only flag).

list
    List live parties on this host from the roster. Parties whose server-access allowlist does not include you are shown as ‘invite-only’ with the host to ask; a party you cannot see at all is either dead or outside your group.

who [--short [--party name]]
    Show invited and attached users for the current party. --short emits a compact form suitable for a status-line widget. --party name selects a party you are not currently hosting.

status
    Show the caller's own state: hosting, attached, or idle.

ENVIRONMENT

TMUX_PARTY_GROUP
    Shared system group used for socket access. When unset, the group is auto-selected at host time; see host. Override to pin an existing group like ‘wheel’, ‘users’, or ‘staff’, or pass --group name to host.

PARTY_GROUP_CANDIDATES
    Space-separated candidate list for host-time group auto-selection. Default: “staff users wheel party”.

PARTY_SOCKET_DIR
    Where each party's per-party private directory (and its socket and roster) is created. Default /tmp. Must not contain whitespace; host refuses such a directory.

PARTY_TMUX
    tmux(1) binary to use. Default ‘tmux’. Override if tmux(1) ≥ 3.3 lives at a non-standard path.

FILES

${PARTY_SOCKET_DIR}/party-${USER}:${NAME}.d/
    Per-party private directory, mode 0750, group TMUX_PARTY_GROUP.

${PARTY_SOCKET_DIR}/party-${USER}:${NAME}.d/sock
    The tmux(1) socket, mode ‘g+rw’.

${PARTY_SOCKET_DIR}/party-${USER}:${NAME}.d/roster
    Bookkeeping: HOST_USER, PARTY_NAME, SOCKET, SERVER_PID, GROUP, CREATED. Security-relevant fields are re-derived from the directory itself on every read, not trusted from the file.

${PARTY_SOCKET_DIR}/party-${USER}:${NAME}.d/.party-notify
    Auto-generated helper that fans display-message out to every attached client on join/leave.

${PARTY_SOCKET_DIR}/party-${USER}:${NAME}.d/log
    Transcript written by log; removed with the party directory unless rescued by close.

EXIT STATUS

0   Success.

1   Operational failure (no such party, not a member of the group, socket gone, ambiguous match, etc.).

2   Usage error (unknown subcommand or flag, missing argument, invalid party name).

13  join: caller is not on the allowlist for the named party.

EXAMPLES
One-time setup, as root:

    groupadd party
    usermod -aG party alice
    usermod -aG party bob

Have alice and bob log out and back in so the new group takes effect.

Host a party named ‘debug-the-deploy’ and invite alice as a watcher and bob as a co-driver:

    $ party host debug-the-deploy
    $ party invite alice -r
    $ party invite bob

On bob's side, join the only running party on this host:

    $ party join

On alice's side, join read-only:

    $ party join debug-the-deploy --passive

Reuse an existing group instead of creating a new one:

    $ party host debug-the-deploy --group staff

SECURITY
The OS group is the perimeter, tmux(1)'s server-access allowlist is the per-user filter. Both are universal across the supported platforms (Linux, FreeBSD, NetBSD, OpenBSD, illumos, Solaris, macOS), so the mechanism is one liner: chgrp(1) + chmod(1). No ACL syscalls are invoked.

Trust is anchored in unforgeable primitives: mkdir(2) and bind(2) set the owner to the calling effective UID, and chown(1) requires root. A group member who plants a forged directory cannot fake its ownership, cannot redirect the socket path, and cannot forge a socket elsewhere.

The threat model is accidents, not adversaries. The ownership and symlink gates in front of destructive operations exist so a stale roster, a typo, or two racing host invocations cannot remove or hijack the wrong directory; that they would also stop a malicious group member is a side effect, not the design goal. New checks should prevent a plausible mistake, not a hypothetical attack from a friend.

party is designed for small, mutually trusted groups: a hacklab, a tech team, a circle of friends, not strangers across the internet. It assumes you already know and trust the people you are adding to the group; it does not try to be a public access-control system.

CAVEATS
On filesystems with NFSv4-style inherited ACLs (macOS HFS+/APFS, FreeBSD/illumos ZFS), an ACE on PARTY_SOCKET_DIR can override the mode bits party sets, widening access (e.g. an inherited ‘everyone@:rx’ survives our ‘chmod 0750’) or narrowing it (e.g. a ‘deny everyone:’ blocks group members our chgrp(1) tried to admit). The script does not invoke getfacl(1) or getextattr(8) to verify effective access; re-introducing per-platform ACL plumbing would unwind the simplification this design commits to. The FS gate is therefore best-effort on those filesystems; the auth gate ( tmux(1) server-access) remains authoritative regardless. The default PARTY_SOCKET_DIR=/tmp rarely has ACL trouble in practice; if you deploy on a custom dir with restrictive inherited ACLs, expect to handle traversal at the ACL layer for your intended group members. Strangers stay out via server-access either way.

server-access decides who may attach, not who can see that a party exists. Hiding a party's existence, meaning the host, its name, group, and creation time recorded in the roster, falls to the FS gate, not the auth gate: on an ACL-shadowed dir where the mode bits do not hold, a non-member may read that metadata even though server-access still blocks the join. The list subcommand re-checks group membership before printing, as defense in depth, but treat discovery as confidential only insofar as the filesystem enforces it.

An active join lands you in your own ‘__party_guest_<you>’ session on the shared server. Any write-capable invitee can create sessions there, including one named for someone else; if they pre-create yours, your next active join attaches to it. This is inside the trust boundary by design, not a defended one; invitees are people you already trust. If you do not trust an invitee that far, invite them read-only (-r), which cannot create sessions, or not at all.

party requires tmux(1) ≥ 3.3 for server-access. Older tmux(1) versions are refused at host time rather than degrading to the FS gate alone.

The role subcommand can flip a client between the guest session and the host session, but it cannot change the read-only state of an attached client; tmux(1) switch-client has no equivalent of attach -r. The watcher boundary is set at join --passive time and is fixed for the life of that client.

Every member of the party's group can read a log transcript, since the per-party directory is the perimeter, including group members who were never invited.

SEE ALSO
tmux(1), screen(1), write(1), chgrp(1), chmod(1)

HISTORY
party was written for the UNIX Social Club (https://club.unix.rocks/), inspired by many years of collective working sessions using ‘screen -x’ across collectives.

AUTHORS
party was written by veg and kol3rby. See the party script header for additional credits. Issues and contributions welcome!