feat(c5): multiplexer session logging (tmux + zellij) #53

Merged
rootiest merged 6 commits from feat/tmux-logging into main 2026-06-16 05:30:03 +00:00
Owner

Summary

Extends C5 logging to terminal multiplexers, with two mechanisms suited to each:

  • tmux (conf.d/tmux-logging.fishfunctions/_tmux_pipe_log.fish): starts tmux pipe-pane to stream the pane continuously to ~/.terminal_history/tmux_<session>-w<win>-p<pane>_<ts>.log whenever fish launches inside tmux. Toggling __fish_config_op_logging stops/restarts the stream in every open shell via the existing --on-variable handlers.
  • zellij (conf.d/zellij-logging.fishfunctions/_zellij_dump_log.fish): zellij has no live-stream facility, so a fish_exit handler snapshots the pane with zellij action dump-screen --full on shell exit, written to ~/.terminal_history/zellij_<session>-p<pane>_<ts>.log. The C5 guard is re-checked inside the handler at exit time, so toggling logging needs no sentinel/sync coordination.
  • shared pruning (functions/_prune_terminal_logs.fish): both helpers delegate max-file pruning here, keeping each prefix within SCROLLBACK_HISTORY_MAX_FILES by mtime — mirroring the paru/yay wrappers.

Why not a tmux function wrapper?

A tmux shell-function wrapper only fires when you type tmux. Panes created via tmux keybindings (prefix+c, splits) are spawned by the tmux server and never go through your shell, so a wrapper would miss them. The conf.d startup hook catches every pane because each one starts a fresh shell.

Bugs caught while building

  • The pruning helper exposed (and now fixes) a latent issue: globbing tmux_*.log directly errored with No matches for wildcard on the first log in an empty dir. The shared helper globs via set (tolerates no-match).
  • Pruning must use command ls/command rm: bare ls is the eza shadow (injects OSC-8 escapes that corrupt paths) and bare rm is the trash wrapper. The paru/yay wrappers sidestep this by being bash.

Manual Verification

tmux

  • Start fish inside tmux → confirm ~/.terminal_history/tmux_*.log is created and captures command output
  • Split the pane / open a new window → confirm a new log file per pane
  • set -U __fish_config_op_logging 0 → pipe-pane stops; set -Ue __fish_config_op_logging → resumes with a new log

zellij

  • Start fish inside zellij, run some commands, then exit → confirm ~/.terminal_history/zellij_*.log exists with the pane scrollback
  • With set -U __fish_config_op_logging 0, exit a zellij pane → confirm no zellij log is written
  • Re-enable and exit again → confirm capture resumes (no shell restart needed)

pruning / general

  • Lower SCROLLBACK_HISTORY_MAX_FILES and open many panes/exits → confirm oldest tmux_*/zellij_* logs are pruned to the limit
  • First-ever log in a fresh ~/.terminal_history → confirm no No matches for wildcard error at startup
  • Non-multiplexer sessions (plain kitty/WezTerm) are unaffected
  • set -U __fish_config_opinionated 0 disables both tmux and zellij capture (master override)
## Summary Extends C5 logging to terminal multiplexers, with two mechanisms suited to each: - **tmux** (`conf.d/tmux-logging.fish` → `functions/_tmux_pipe_log.fish`): starts `tmux pipe-pane` to stream the pane continuously to `~/.terminal_history/tmux_<session>-w<win>-p<pane>_<ts>.log` whenever fish launches inside tmux. Toggling `__fish_config_op_logging` stops/restarts the stream in every open shell via the existing `--on-variable` handlers. - **zellij** (`conf.d/zellij-logging.fish` → `functions/_zellij_dump_log.fish`): zellij has no live-stream facility, so a `fish_exit` handler snapshots the pane with `zellij action dump-screen --full` on shell exit, written to `~/.terminal_history/zellij_<session>-p<pane>_<ts>.log`. The C5 guard is re-checked inside the handler at exit time, so toggling logging needs no sentinel/sync coordination. - **shared pruning** (`functions/_prune_terminal_logs.fish`): both helpers delegate max-file pruning here, keeping each prefix within `SCROLLBACK_HISTORY_MAX_FILES` by mtime — mirroring the paru/yay wrappers. ## Why not a `tmux` function wrapper? A `tmux` shell-function wrapper only fires when you type `tmux`. Panes created via tmux keybindings (prefix+c, splits) are spawned by the tmux server and never go through your shell, so a wrapper would miss them. The conf.d startup hook catches every pane because each one starts a fresh shell. ## Bugs caught while building - The pruning helper exposed (and now fixes) a latent issue: globbing `tmux_*.log` directly errored with `No matches for wildcard` on the **first** log in an empty dir. The shared helper globs via `set` (tolerates no-match). - Pruning must use `command ls`/`command rm`: bare `ls` is the eza shadow (injects OSC-8 escapes that corrupt paths) and bare `rm` is the trash wrapper. The paru/yay wrappers sidestep this by being bash. ## Manual Verification **tmux** - [x] Start fish inside tmux → confirm `~/.terminal_history/tmux_*.log` is created and captures command output - [x] Split the pane / open a new window → confirm a new log file per pane - [x] `set -U __fish_config_op_logging 0` → pipe-pane stops; `set -Ue __fish_config_op_logging` → resumes with a new log **zellij** - [x] Start fish inside zellij, run some commands, then `exit` → confirm `~/.terminal_history/zellij_*.log` exists with the pane scrollback - [x] With `set -U __fish_config_op_logging 0`, exit a zellij pane → confirm no zellij log is written - [x] Re-enable and exit again → confirm capture resumes (no shell restart needed) **pruning / general** - [x] Lower `SCROLLBACK_HISTORY_MAX_FILES` and open many panes/exits → confirm oldest `tmux_*`/`zellij_*` logs are pruned to the limit - [x] First-ever log in a fresh `~/.terminal_history` → confirm no `No matches for wildcard` error at startup - [x] Non-multiplexer sessions (plain kitty/WezTerm) are unaffected - [x] `set -U __fish_config_opinionated 0` disables both tmux and zellij capture (master override)
rootiest added 2 commits 2026-06-16 04:54:38 +00:00
Starts tmux pipe-pane capture for the current pane whenever fish
launches inside a tmux session. Each shell gets a timestamped log
in SCROLLBACK_HISTORY_DIR. Toggling __fish_config_op_logging stops
or restarts the pipe in all open fish shells via the existing
--on-variable event handlers.
rootiest added 1 commit 2026-06-16 04:56:01 +00:00
Fish does not support bash-style ${var} brace syntax. Build the
log file path in a dedicated variable using quote-splicing instead.
rootiest added 1 commit 2026-06-16 05:04:02 +00:00
Extract the tmux pipe-pane setup into functions/_tmux_pipe_log.fish,
called by both conf.d/tmux-logging.fish (startup) and
__fish_config_sync_logging (C5 re-enable). The helper prunes the oldest
tmux_*.log files by mtime to stay within SCROLLBACK_HISTORY_MAX_FILES,
matching the paru/yay wrappers.

Uses 'command ls' to bypass the eza ls shadow, which injects OSC-8
hyperlink escapes into paths and corrupted the filenames passed to rm.
rootiest added 1 commit 2026-06-16 05:16:36 +00:00
Zellij has no live-stream facility like tmux pipe-pane, so capture is a
one-shot 'zellij action dump-screen --full' run from a fish_exit handler
(conf.d/zellij-logging.fish -> functions/_zellij_dump_log.fish). The C5
guard is checked inside the helper at exit time, so toggling logging
needs no sync_logging coordination — there is no persistent stream.

Extract pruning into functions/_prune_terminal_logs.fish, shared by both
the tmux and zellij helpers. This also fixes a latent bug in the tmux
helper: globbing tmux_*.log directly errored with 'No matches for
wildcard' on the first log in an empty dir. The shared helper globs via
'set' (tolerates no-match) and uses command ls/rm to bypass the eza and
trash C1 shadows.
rootiest changed title from feat(c5): tmux pipe-pane session logging to feat(c5): multiplexer session logging (tmux + zellij) 2026-06-16 05:16:55 +00:00
rootiest added 1 commit 2026-06-16 05:27:51 +00:00
Add a Session Logging section and a CAUTION callout to the README, and a
matching privacy notice to docs/fish-config.md, making clear that this
config logs terminal sessions (Kitty/tmux/zellij/paru/yay) to
~/.terminal_history by default. Point users at both the
__fish_config_op_logging variable and the interactive config-toggle TUI
for opting out.
rootiest added the Kind/EnhancementKind/Feature labels 2026-06-16 05:29:57 +00:00
rootiest merged commit 429ccc3876 into main 2026-06-16 05:30:03 +00:00
rootiest deleted branch feat/tmux-logging 2026-06-16 05:30:04 +00:00
Sign in to join this conversation.