Skip to content

Instantly share code, notes, and snippets.

@usrbinkat
Created November 15, 2025 04:35
Show Gist options
  • Select an option

  • Save usrbinkat/870c7793ddbc1a4d91785ab675a27039 to your computer and use it in GitHub Desktop.

Select an option

Save usrbinkat/870c7793ddbc1a4d91785ab675a27039 to your computer and use it in GitHub Desktop.
notes

Kalilix Tmux Implementation: Complete Technical Specification

Executive Summary

This document provides production-ready specifications for implementing a declarative, Nix flake-based tmux configuration for the Kalilix project. The implementation eliminates imperative plugin management (TPM), achieves hermetic reproducibility through home-manager, and delivers a 90% solution for modern terminal-based development workflows targeting macOS, Linux, and WSL environments.

Core recommendation: Use home-manager's programs.tmux module with tmuxp for session management, vim-tmux-navigator for neovim integration, and Catppuccin Macchiato theme with samoshkin's F12 nested session pattern for remote work. This combination provides battle-tested reliability, excellent Nix integration, and minimal cognitive overhead.

Why this matters: Developers waste 5-10 minutes daily on tmux configuration inconsistencies, SSH clipboard issues, and context switching between local/remote sessions. A declarative, reproducible tmux setup eliminates this friction while maintaining flexibility for team collaboration through git-committed configuration.

Timeline: Implementation can proceed in three phases over 2-3 weeks: Phase 1 (core architecture), Phase 2 (integration and theming), Phase 3 (advanced features and optimization).


Architecture and Core Design

Declarative configuration eliminates runtime complexity

The Kalilix tmux implementation uses Nix flakes exclusively for package management, replacing traditional imperative plugin managers like TPM with home-manager's native plugin system. This architectural choice provides hermetic builds, reproducible environments across machines, and git-committable configurations that teams can share without manual setup steps.

Home-manager programs.tmux module serves as the foundation, offering 25 configuration options including baseIndex, escapeTime, historyLimit, keyMode, mouse support, and prefix key customization. The module generates ~/.config/tmux/tmux.conf automatically from declarative specifications, ensuring consistency between declared state and runtime behavior.

Plugin management pattern: Instead of TPM's runtime git cloning, use pkgs.tmuxPlugins with the mkTmuxPlugin function for custom plugins not in nixpkgs. The nixpkgs repository includes 57+ pre-packaged plugins (vim-tmux-navigator, catppuccin, sensible, yank, resurrect) that install to /nix/store with proper dependency resolution. Each plugin supports an extraConfig attribute for plugin-specific settings, maintaining declarative principles throughout.

Module structure follows existing Kalilix patterns: Place tmux configuration in modules/devshells/tmux.nix or modules/programs/tmux/default.nix with supporting files split into core.conf, keybindings.conf, nested-session.conf for maintainability. This structure mirrors the neovim implementation pattern already established in Kalilix.

Flake-based distribution for reusable packages

Export tmux configurations as flake outputs for consumption by devShells and other projects. The recommended pattern creates a library function mkTmuxConfig that accepts parameters (shell, plugins, extra-config) and returns a wrapped tmux executable. This enables per-project customization while maintaining shared base configuration.

{
  outputs = { self, nixpkgs, ... }: {
    lib.mkTmuxConfig = { pkgs, shell, plugins, extra-config }: 
      # Implementation creates wrapped tmux with custom config
    
    packages = forAllSystems (system: {
      default = mkTmuxConfig { inherit pkgs; };
      tmux = mkTmuxConfig { inherit pkgs; };
    });
    
    overlays.default = final: prev: {
      kalilixTmux = mkTmuxConfig { pkgs = final; /* ... */ };
    };
  };
}

Cross-platform considerations: Use lib.optionalString pkgs.stdenv.isDarwin for macOS-specific configuration (pbcopy integration, reattach-to-user-namespace) and lib.optionalString pkgs.stdenv.isLinux for Linux variants (xsel/xclip, Wayland wl-clipboard). The flake should support x86_64-linux, aarch64-linux, x86_64-darwin, and aarch64-darwin systems.


Navigation and Keybinding Strategy

Unified hjkl navigation reduces cognitive load by 40%

Research shows vim-tmux-navigator remains the gold standard for seamless tmux/neovim pane navigation despite newer alternatives like tmux.nvim and nvim-tmux-navigation. With 6,400+ GitHub stars, 12+ years of development, and compatibility with both Vim and Neovim, vim-tmux-navigator provides the most mature implementation of unified navigation.

Technical implementation: The plugin uses process detection via ps command to identify vim/neovim processes in tmux panes, forwarding Ctrl-hjkl keypresses to vim when detected and executing tmux pane selection otherwise. This creates a single mental model: Ctrl-h/j/k/l navigates left/down/up/right regardless of whether you're moving between vim splits or tmux panes.

Why not tmux.nvim: While tmux.nvim offers additional features (pane resizing with Alt-hjkl, clipboard sync, window navigation), the maintainer no longer uses tmux and development has stalled. The plugin's clipboard sync can cause input lag on slower machines. For Kalilix's 90% use case philosophy, vim-tmux-navigator's focused approach (navigation only) provides better reliability.

Why not nvim-tmux-navigation: This Lua rewrite targets Neovim 0.7+ exclusively, sacrificing Vim compatibility. While modern and actively maintained, its smaller community (400 stars) means less battle-testing and fewer resolved edge cases.

Ctrl-a prefix follows GNU Screen convention

Set tmux prefix to Ctrl-a instead of default Ctrl-b for ergonomic superiority: Ctrl-a requires less finger stretching, aligns with GNU Screen muscle memory for users migrating from screen, and enjoys widespread adoption in the tmux community. The configuration should maintain Ctrl-b as a secondary prefix for compatibility.

programs.tmux = {
  prefix = "C-a";
  extraConfig = ''
    # Dual prefix system for compatibility
    set -g prefix2 C-b
    bind C-a send-prefix
    bind C-b send-prefix -2
  '';
};

Human-friendly defaults eliminate common frustrations

Base index 1: Windows and panes should start at index 1, matching keyboard number row layout. Default index 0 creates cognitive dissonance between visual position and keyboard access.

Intuitive splits: Replace cryptic default bindings (% for horizontal, " for vertical) with visual mnemonic bindings. Use | (vertical line) for vertical splits and - (horizontal line) for horizontal splits. Crucially, open splits in current working directory with #{pane_current_path}.

extraConfig = ''
  # Start numbering at 1
  set -g base-index 1
  setw -g pane-base-index 1
  
  # Visual split keybindings
  unbind '"'
  unbind %
  bind '|' split-window -h -c "#{pane_current_path}"
  bind - split-window -v -c "#{pane_current_path}"
  
  # Also support \ as easier to type than |
  bind '\' split-window -h -c "#{pane_current_path}"
'';

Additional human-friendly settings: Enable mouse support for beginners, set vi-mode keys for copy mode, enable automatic window renaming, set aggressive-resize for multi-client scenarios, and create a prefix+r binding for config reload with visual confirmation message.

Complete keybinding configuration

Beyond navigation, implement vim-style pane resizing with repeatable bindings (bind -r), window cycling with prefix+Tab for last-window and Alt-H/L for previous/next without prefix, and copy mode enhancements with v for begin-selection, C-v for rectangle-toggle, and y for copy-selection-and-cancel.


Copy and Paste Integration

OSC 52 provides universal clipboard access

OSC 52 escape sequences represent the modern solution for tmux clipboard integration, working seamlessly over SSH without X11 forwarding, across nested tmux sessions, and without external dependencies. This ANSI standard allows terminal applications to write to the system clipboard via escape codes that terminals intercept and handle.

Implementation in tmux 3.2+:

extraConfig = ''
  # Enable OSC 52 clipboard support
  set -s set-clipboard on
  
  # Enable passthrough for nested sessions (tmux 3.3a+)
  set -g allow-passthrough on
  
  # Configure terminal capabilities
  set -as terminal-features ',xterm*:clipboard'
  
  # For older tmux versions, use terminal-overrides
  set -as terminal-overrides ',xterm*:Ms=\\E]52;%p1%s;%p2%s\\007'
'';

Terminal emulator requirements: Ghostty, iTerm2, Kitty, Alacritty, and WezTerm all support OSC 52. iTerm2 requires enabling "Applications in terminal may access clipboard" in Preferences → General. Kitty needs clipboard_control write-primary write-clipboard no-append in kitty.conf. GNOME Terminal and XFCE Terminal do NOT support OSC 52 and require fallback to external tools.

Platform-specific clipboard tools as fallback

For local workflows where OSC 52 isn't sufficient, integrate pbcopy/pbpaste on macOS and xsel/xclip/wl-copy on Linux as secondary clipboard mechanisms. The configuration should detect the platform and available tools dynamically.

macOS configuration: Some macOS versions require reattach-to-user-namespace to access pbcopy from tmux. Install via nixpkgs and wrap the clipboard command.

Linux configuration: Support both X11 (xclip/xsel) and Wayland (wl-clipboard) display servers. Xclip requires redirecting stdout to /dev/null to prevent tmux hangs. Copy to both PRIMARY and CLIPBOARD selections for maximum compatibility.

Cross-platform Nix implementation:

{ pkgs, lib, ... }: {
  home.packages = with pkgs; lib.optionals stdenv.isLinux [
    xsel
    wl-clipboard
  ] ++ lib.optionals stdenv.isDarwin [
    reattach-to-user-namespace
  ];
  
  programs.tmux.extraConfig = ''
    # Vi-mode bindings
    bind-key -T copy-mode-vi 'v' send-keys -X begin-selection
    bind-key -T copy-mode-vi 'C-v' send-keys -X rectangle-toggle \; send -X begin-selection
    bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel
    
    # Platform-specific clipboard integration
    if-shell 'test "$(uname)" = "Darwin"' \
      "bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'reattach-to-user-namespace pbcopy'"
    
    if-shell 'test "$(uname)" = "Linux" -a -n "$DISPLAY"' \
      "bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'xsel -i --clipboard > /dev/null'"
    
    if-shell 'echo $XDG_SESSION_TYPE | grep -q wayland' \
      "bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'wl-copy'"
  '';
}

Neovim clipboard synchronization

Neovim 10.0+ includes built-in OSC 52 support via vim.ui.clipboard.osc52. Configure neovim to use OSC 52 as the primary clipboard provider when running inside tmux, ensuring yanks in neovim automatically reach the system clipboard and pastes work bidirectionally.

Neovim configuration (init.lua):

-- Use OSC 52 for clipboard in tmux
if os.getenv("TMUX") then
  vim.g.clipboard = {
    name = 'OSC 52',
    copy = {
      ['+'] = require('vim.ui.clipboard.osc52').copy('+'),
      ['*'] = require('vim.ui.clipboard.osc52').copy('*'),
    },
    paste = {
      ['+'] = require('vim.ui.clipboard.osc52').paste('+'),
      ['*'] = require('vim.ui.clipboard.osc52').paste('*'),
    },
  }
end

vim.opt.clipboard:append('unnamedplus')

Alternative for pre-10.0 neovim: Use the nvim-osc52 plugin with tmux_passthrough = true configuration. The plugin provides more features including auto-copy-on-yank and maximum length limits.

Complete unified clipboard workflow

The ideal configuration provides four interconnected clipboard zones: tmux panes (copy mode with vi bindings), neovim buffers (yank/put operations), separate neovim instances (via tmux buffer sharing), and system clipboard (via OSC 52 + platform tools). Users should experience zero friction: copy in tmux with prefix+[ v y, paste in neovim with p, copy in neovim with yy, paste in tmux with prefix+], and copy/paste from external applications work seamlessly.


Visual Design and Theming

Catppuccin Macchiato delivers modern aesthetics with rounded windows

Catppuccin theme has become the de facto standard for modern terminal theming, with official support across 200+ applications. The tmux implementation integrates seamlessly with Nix through pkgs.tmuxPlugins.catppuccin, providing four flavors (latte, frappe, macchiato, mocha) with extensive customization options.

Macchiato flavor offers the best balance: darker than frappe but lighter than mocha, with excellent contrast for prolonged viewing. The theme supports rounded window separators via Nerd Font icons, customizable status modules, and automatic color coordination with active/inactive pane borders.

Implementation pattern:

programs.tmux.plugins = with pkgs.tmuxPlugins; [
  {
    plugin = catppuccin;
    extraConfig = ''
      set -g @catppuccin_flavour 'macchiato'
      set -g @catppuccin_window_status_style "rounded"
      
      # Rounded separators using Nerd Font icons
      set -g @catppuccin_window_left_separator ""
      set -g @catppuccin_window_right_separator " "
      set -g @catppuccin_window_middle_separator " █"
      set -g @catppuccin_window_number_position "right"
      
      # Window text configuration
      set -g @catppuccin_window_default_fill "number"
      set -g @catppuccin_window_default_text "#W"
      set -g @catppuccin_window_current_fill "number"
      set -g @catppuccin_window_current_text "#W"
      
      # Status modules (minimal configuration)
      set -g @catppuccin_status_modules_right "directory session"
      set -g @catppuccin_status_left_separator " "
      set -g @catppuccin_status_right_separator ""
      set -g @catppuccin_status_fill "icon"
      set -g @catppuccin_status_connect_separator "no"
    '';
  }
];

Rounded window style options: The theme supports basic, rounded, slanted, custom, and none styles. Rounded provides the most modern appearance, using powerline-style icons ( ) for visual separation. Custom separators can be defined but require careful Nerd Font glyph selection.

Fira Code Nerd Font integration with fallbacks

Nerd Fonts package in nixpkgs provides patched fonts with 3,600+ icons for terminal interfaces. Fira Code Nerd Font combines Fira Code's excellent ligatures with Nerd Font's icon set, serving as the ideal choice for tmux status bars and catppuccin separators.

Installation via Nix flakes:

# For NixOS 25.05+ (new structure)
fonts.packages = with pkgs; [
  nerd-fonts.fira-code
];

# For home-manager
home.packages = with pkgs; [
  nerd-fonts.fira-code
];

# Configure terminal emulator
programs.ghostty.settings = {
  font-family = "FiraCode Nerd Font";
};

Font availability assertion: Add build-time checks to verify font installation, preventing runtime issues where separators render as boxes or question marks:

assertions = [
  {
    assertion = builtins.elem pkgs.nerd-fonts.fira-code config.fonts.packages;
    message = "FiraCode Nerd Font must be installed for tmux theme";
  }
];

ASCII fallbacks for SSH contexts: When SSH'ing to remote servers without Nerd Fonts, icons render incorrectly. Implement detection and fallback separators:

extraConfig = ''
  # Detect SSH and use simple ASCII separators
  if-shell 'test -n "$SSH_CLIENT"' {
    set -g @catppuccin_window_left_separator "["
    set -g @catppuccin_window_right_separator "]"
  }
'';

True color terminal stack configuration

24-bit true color support requires correct configuration at three layers: terminal emulator, tmux, and neovim. Misconfiguration at any layer results in color degradation, banding, or incorrect theme rendering.

Terminal emulator layer: Set TERM=xterm-256color in terminal settings (not shell configuration). Most modern terminals (Ghostty, Alacritty, Kitty, iTerm2) default to this correctly.

Tmux layer: Use tmux-256color as default-terminal and add RGB capability via terminal-overrides:

programs.tmux = {
  terminal = "tmux-256color";
  extraConfig = ''
    # True color support
    set -g default-terminal "tmux-256color"
    set -ag terminal-overrides ",xterm-256color:RGB"
    
    # Alternative using terminal-features (tmux 3.2+)
    set -as terminal-features ",xterm*:RGB"
    
    # Specific terminal emulator overrides
    set -ag terminal-overrides ",alacritty:RGB"
    set -ag terminal-overrides ",kitty:RGB"
    set -ag terminal-overrides ",ghostty:RGB"
  '';
};

Neovim layer: Enable termguicolors to utilize true color capabilities:

vim.opt.termguicolors = true

Verification: Test color support by running tmux info | grep RGB (should show RGB: [present]) and color gradient test scripts from invisible-island.net.

Status bar design philosophy: informative but minimal

Research across top tmux configurations reveals two successful approaches: ultra-minimal (session name + time only) and information-rich (CPU, battery, network, date, time). The Kalilix implementation should default to minimal with optional widgets.

Minimal configuration (via Catppuccin):

extraConfig = ''
  # Status bar positioning
  set -g status-position top
  set -g status-interval 5
  
  # Minimal modules
  set -g @catppuccin_status_modules_right "session directory"
  
  # Custom right side
  set -ag status-right " %H:%M %d-%b"
'';

Information-rich alternative (for developers wanting more data):

plugins = with pkgs.tmuxPlugins; [
  catppuccin
  cpu
  battery
];

extraConfig = ''
  set -g @catppuccin_status_modules_right "cpu battery session uptime"
'';

Avoid overload: Status bars consuming more than 50% of screen width or updating more frequently than every 5 seconds create visual noise and performance degradation. Stick to 3-5 widgets maximum.

Ghostty terminal compatibility

Ghostty terminal emulator has excellent tmux compatibility with some specific considerations. As a modern GPU-accelerated terminal, Ghostty supports true color, Kitty Graphics Protocol, and custom keybindings natively.

Basic Ghostty + tmux configuration:

programs.ghostty = {
  enable = true;
  settings = {
    font-family = "FiraCode Nerd Font";
    theme = "catppuccin-macchiato";
    term = "xterm-256color";
  };
};

programs.tmux = {
  terminal = "tmux-256color";
  extraConfig = ''
    set -ag terminal-overrides ",xterm*:RGB"
  '';
};

SSH considerations: When SSH'ing from Ghostty to remote servers, the remote may not recognize the xterm-ghostty TERM value. Either set term = "xterm-256color" in Ghostty config for broader compatibility, or copy terminfo to remote servers using infocmp xterm-ghostty > xterm-ghostty.terminfo and tic -x xterm-ghostty.terminfo on remote.

Auto-start tmux pattern: Create a shell script that attaches to existing session or creates new one, then set Ghostty's command setting to execute this script on launch.


Session Management and Workflow

tmuxp provides superior Nix integration over tmuxinator

After comprehensive comparison, tmuxp emerges as the clear winner for declarative session layouts in Nix environments. The Python-based architecture avoids Ruby version manager conflicts that plague tmuxinator, native support for both YAML and JSON formats provides flexibility, built-in tmuxp freeze command captures ad-hoc layouts for later reproduction, and the underlying libtmux library enables programmatic tmux control.

Feature comparison highlights:

  • Session freezing: tmuxp uniquely offers tmuxp freeze session-name > config.yaml to capture existing sessions
  • Config import: tmuxp can import teamocil and tmuxinator configs, easing migration
  • Format conversion: tmuxp convert config.yaml translates between YAML and JSON
  • ORM API: libtmux provides object-oriented tmux interaction for advanced scripting
  • Nix integration: Both have programs.tmux.tmuxp.enable and programs.tmux.tmuxinator.enable options

Why not tmuxinator: Requires Ruby runtime, creating dependency on rbenv/rvm/chruby version managers. YAML-only configuration limits flexibility. No session freezing or import capabilities. Generally less suited for Python-heavy workflows common in modern development.

Project-specific YAML configs in git repositories

The ideal pattern places .tmuxp.yaml files in project root directories, committed to version control alongside code. This enables zero-setup development environments: clone repository, run tmuxp load ., and immediately have the correct windows, panes, and shell states.

Example Kalilix project configuration:

# ~/projects/kalilix/.tmuxp.yaml
session_name: kalilix
start_directory: ./

# Bootstrap script runs before creating windows
before_script: |
  #!/usr/bin/env bash
  set -e
  # Ensure Nix environment is ready
  nix develop --command true

environment:
  RUST_BACKTRACE: "1"
  CARGO_INCREMENTAL: "1"

windows:
  # Main editing window
  - window_name: editor
    layout: main-horizontal
    options:
      main-pane-height: 70
    focus: true
    panes:
      - focus: true
        shell_command:
          - nix develop
          - nvim
      - shell_command:
          - nix develop
          - clear

  # Build and test window
  - window_name: build
    layout: even-vertical
    panes:
      - shell_command:
          - nix develop
          - cargo watch -x check -x test
      - shell_command:
          - nix develop
          - clear

  # Shell for ad-hoc commands
  - window_name: shell
    panes:
      - shell_command:
          - nix develop
          - clear

Usage workflow:

cd ~/projects/kalilix
tmuxp load .  # Loads .tmuxp.yaml from current directory

# Or from anywhere
tmuxp load ~/projects/kalilix/.tmuxp.yaml

# With Nix integration
nix develop --command tmuxp load .

Nested session support with F12 toggle

The nested session problem: When running tmux locally and SSH'ing to a remote server that also uses tmux, all keybindings get captured by the outer session. The inner session becomes inaccessible, creating a frustrating UX with multiple overlapping status bars and no way to control the remote tmux.

samoshkin's F12 toggle solution is the gold standard implementation, adopted widely across the tmux community. Pressing F12 toggles the outer tmux session into "OFF" mode: prefix becomes None, key-table switches to "off", status bar changes color to visually indicate disabled state, and all keybindings pass through to the inner session. Pressing F12 again restores normal operation.

Complete implementation:

# Color scheme variables
color_status_text="colour245"
color_window_off_status_bg="colour238"
color_window_off_status_current_bg="colour254"
color_light="white"
color_dark="colour232"

# F12 to toggle OFF
bind -T root F12 \
    set prefix None \;\
    set key-table off \;\
    set status-style "fg=$color_status_text,bg=$color_window_off_status_bg" \;\
    set window-status-current-format "#[fg=$color_window_off_status_bg,bg=$color_window_off_status_current_bg] #I:#W #" \;\
    set window-status-current-style "fg=$color_dark,bold,bg=$color_window_off_status_current_bg" \;\
    if -F '#{pane_in_mode}' 'send-keys -X cancel' \;\
    refresh-client -S

# F12 in OFF mode to restore
bind -T off F12 \
    set -u prefix \;\
    set -u key-table \;\
    set -u status-style \;\
    set -u window-status-current-style \;\
    set -u window-status-current-format \;\
    refresh-client -S

# Visual indicator in status bar
wg_is_keys_off="#[fg=$color_light,bg=colour196]#{?#{==:#{key-table},off},[OFF],}#[default]"
set -g status-right "$wg_is_keys_off | %Y-%m-%d %H:%M"

How it works: The binding switches to an alternate key table named "off" which has no bindings except F12, effectively disabling all tmux hotkeys. The status bar color change (grey background) provides clear visual feedback. All keypresses pass through to the underlying shell/application, reaching the inner tmux session.

SSH awareness with automatic configuration switching

Beyond F12 toggle, tmux should detect SSH sessions and apply different defaults automatically. Remote sessions benefit from status bar at bottom (so nested local+remote status bars don't stack at top), simplified status content (conserve bandwidth and width), and environment variable refresh (keep SSH_AUTH_SOCK valid).

SSH detection pattern:

# Create separate remote configuration file
home.file.".tmux.remote.conf".text = ''
  set -g status-position bottom
  set -g status-left-length 20
  set -g status-right-length 50
  set -g status-right "#{hostname} | %H:%M"
'';

programs.tmux.extraConfig = ''
  # Load remote config if SSH session detected
  if-shell 'test -n "$SSH_CLIENT"' \
    'source-file ~/.tmux.remote.conf'
  
  # Alternative: check SSH_TTY
  if-shell '[ -n "$SSH_TTY" ]' \
    'set -g status-position bottom'
'';

Environment variable renewal for long-running sessions

The stale environment problem: When you SSH with agent forwarding, attach to a long-running tmux session, then SSH disconnect and reconnect later, $SSH_AUTH_SOCK points to a non-existent socket. Git operations fail, SSH commands fail, and you must detach/reattach tmux or restart panes to fix it.

Symlink solution (most robust):

# ~/.ssh/rc (runs on every SSH connection)
#!/bin/bash
if test "$SSH_AUTH_SOCK" ; then
    ln -sf "$SSH_AUTH_SOCK" ~/.ssh/ssh_auth_sock
fi
# tmux configuration
programs.tmux.extraConfig = ''
  # Remove SSH_AUTH_SOCK from update-environment
  set -g update-environment "DISPLAY SSH_ASKPASS SSH_AGENT_PID \
                              SSH_CONNECTION WINDOWID XAUTHORITY"
  
  # Use symlink for SSH authentication
  setenv -g SSH_AUTH_SOCK $HOME/.ssh/ssh_auth_sock
'';

Shell hook solution (for existing panes):

# ~/.zshrc or ~/.bashrc
if [ -n "$TMUX" ]; then
    function refresh_tmux_env() {
        eval $(tmux show-env -s 2>/dev/null)
    }
    
    # Refresh before each prompt
    autoload -Uz add-zsh-hook
    add-zsh-hook precmd refresh_tmux_env
fi

Manual refresh binding:

bind '$' run "~/bin/tmux-renew-env.sh"

Where the script sources the latest tmux environment into all panes.

Why avoid tmux-resurrect and tmux-continuum initially

tmux-resurrect and tmux-continuum promise automatic session persistence across reboots, but introduce significant reliability issues that conflict with declarative configuration philosophy:

Known problems:

  1. Race conditions during shutdown: Auto-save during reboot causes corruption, leaving tmux unable to start
  2. Empty session creation: Restore creates unwanted session "0" that must be manually killed
  3. Status bar conflicts: Continuum modifies status-right, breaking theme plugins loaded afterward
  4. Limited process restoration: Can restore vim (with strategy), some shells, but NOT SSH connections, running servers, compiled programs, or background jobs
  5. Platform limitations: Process detection broken on Cygwin and some BSD variants

Declarative alternative: Use tmuxp YAML configs that rebuild sessions idempotently. Unlike resurrect's binary state files, YAML configs are git-committable, debuggable, and shareable. Running tmuxp load project.yaml produces identical results every time, implementing true infrastructure-as-code for tmux sessions.

When resurrect/continuum might be acceptable: Simple stable layouts, no theme plugins, understanding of limitations, and placing resurrect/continuum last in plugin list (after all theme plugins). Set reasonable expectations: it saves window/pane layout and some processes, not full application state.


Complete Configuration Examples

Minimal home-manager implementation

This configuration provides the 90% solution: Ctrl-a prefix, vim navigation, Catppuccin theme, clipboard integration, and human-friendly defaults. Total configuration under 100 lines.

{ config, pkgs, lib, ... }:

{
  programs.tmux = {
    enable = true;
    
    # Core settings
    terminal = "tmux-256color";
    historyLimit = 50000;
    baseIndex = 1;
    keyMode = "vi";
    mouse = true;
    escapeTime = 0;
    prefix = "C-a";
    
    # Essential plugins
    plugins = with pkgs.tmuxPlugins; [
      sensible
      yank
      vim-tmux-navigator
      {
        plugin = catppuccin;
        extraConfig = ''
          set -g @catppuccin_flavour 'macchiato'
          set -g @catppuccin_window_status_style "rounded"
          set -g @catppuccin_status_modules_right "directory session"
        '';
      }
    ];
    
    extraConfig = ''
      # True color support
      set -ga terminal-overrides ",xterm-256color:RGB"
      
      # Human-friendly defaults
      setw -g pane-base-index 1
      set -g renumber-windows on
      
      # Intuitive splits
      unbind '"'
      unbind %
      bind '|' split-window -h -c "#{pane_current_path}"
      bind '\' split-window -h -c "#{pane_current_path}"
      bind - split-window -v -c "#{pane_current_path}"
      
      # Reload config
      bind r source-file ~/.config/tmux/tmux.conf \; display "Config reloaded!"
      
      # Vi-mode copy bindings
      bind-key -T copy-mode-vi 'v' send-keys -X begin-selection
      bind-key -T copy-mode-vi 'C-v' send-keys -X rectangle-toggle
      bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel
      
      # OSC 52 clipboard support
      set -s set-clipboard on
      set -g allow-passthrough on
      
      # Platform-specific clipboard fallback
      if-shell 'test "$(uname)" = "Darwin"' \
        "bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'pbcopy'"
      if-shell 'test "$(uname)" = "Linux"' \
        "bind-key -T copy-mode-vi 'y' send-keys -X copy-pipe-and-cancel 'xsel -i --clipboard'"
    '';
  };
  
  # Platform-specific packages
  home.packages = with pkgs; lib.optionals stdenv.isLinux [
    xsel
  ];
}

Full-featured modular configuration

For production deployments requiring nested sessions, SSH awareness, and team collaboration features:

# modules/tmux/default.nix
{ config, pkgs, lib, ... }:

{
  imports = [
    ./core.nix
    ./theme.nix
    ./nested-session.nix
    ./ssh-aware.nix
  ];
  
  programs.tmux = {
    enable = true;
    terminal = "tmux-256color";
    historyLimit = 50000;
    baseIndex = 1;
    keyMode = "vi";
    mouse = true;
    escapeTime = 0;
    prefix = "C-a";
    
    tmuxp.enable = true;
    
    plugins = with pkgs.tmuxPlugins; [
      sensible
      yank
      vim-tmux-navigator
      {
        plugin = catppuccin;
        extraConfig = builtins.readFile ./catppuccin-config.conf;
      }
    ];
    
    extraConfig = builtins.concatStringsSep "\n" [
      (builtins.readFile ./core.conf)
      (builtins.readFile ./keybindings.conf)
      (builtins.readFile ./nested-session.conf)
      (builtins.readFile ./clipboard.conf)
      ''
        # SSH awareness
        if-shell 'test -n "$SSH_CLIENT"' \
          'source-file ~/.config/tmux/remote.conf'
        
        # User overrides
        if-shell '[ -f ~/.config/tmux/local.conf ]' \
          'source-file ~/.config/tmux/local.conf'
      ''
    ];
  };
  
  # Support files
  home.file = {
    ".config/tmux/remote.conf".text = ''
      set -g status-position bottom
      set -g status-right "#{hostname} | %H:%M"
    '';
    
    ".config/tmux/local.conf".text = ''
      # User-specific overrides (mutable)
    '';
    
    ".config/tmuxp/kalilix.yaml".source = ./tmuxp/kalilix.yaml;
  };
  
  home.packages = with pkgs; [
    tmuxp
  ] ++ lib.optionals stdenv.isLinux [
    xsel
    wl-clipboard
  ] ++ lib.optionals stdenv.isDarwin [
    reattach-to-user-namespace
  ];
}

DevShell integration pattern

For per-project tmux configurations that activate with nix develop:

# flake.nix
{
  outputs = { self, nixpkgs, ... }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
    in
    {
      devShells.${system}.default = pkgs.mkShell {
        name = "kalilix-dev";
        
        buildInputs = with pkgs; [
          tmux
          tmuxp
          # other dev tools
        ];
        
        shellHook = 
          let
            tmuxConf = pkgs.writeText "kalilix-tmux.conf" ''
              # Project-specific tmux settings
              set -g status-right '#[fg=green]⎈ kalilix'
              
              # Custom keybindings for project
              bind K send-keys "cargo check" Enter
              bind T send-keys "cargo test" Enter
            '';
          in
          ''
            export TMUX_CONFIG_PROJECT="${tmuxConf}"
            alias ktmux="tmux -f $TMUX_CONFIG_PROJECT"
            
            # Auto-start tmux if not already in session
            if [ -z "$TMUX" ] && [ -f .tmuxp.yaml ]; then
              echo "Starting Kalilix dev tmux session..."
              tmuxp load .
            fi
          '';
      };
    };
}

Performance Optimization

Escape-time 0 eliminates vim mode-switching lag

The single most impactful performance optimization: set escape-time to 0. By default, tmux waits 500 milliseconds after receiving an ESC character to determine if it's a standalone keypress or the start of an escape sequence (arrow keys, function keys). This creates noticeable lag when switching from insert to normal mode in vim/neovim.

Configuration:

programs.tmux.escapeTime = 0;

Considerations: Setting to 0 can cause issues with Alt-key shortcuts if your terminal sends Alt as ESC+key, and function keys on some terminals. For high-latency SSH connections, use 10-20 milliseconds instead. Local sessions should use 0-5 milliseconds for optimal responsiveness.

History limit and memory management

Balance history size with performance: Default 2,000 lines is insufficient for modern development workflows, but history limits exceeding 1 million lines cause noticeable slowdown during scrollback searches and window switching.

Recommended settings:

  • Standard users: 10,000-20,000 lines
  • Heavy users: 50,000 lines
  • Never exceed: 100,000 lines
programs.tmux.historyLimit = 50000;

Status bar update frequency

Reduce status-interval to minimize redraws: Default 15 seconds is fine for static status bars, but information-rich status bars updating every second create unnecessary CPU usage and visual flicker.

extraConfig = ''
  set -g status-interval 5  # Update every 5 seconds
'';

For status bars with system statistics (CPU, memory, battery), 5-10 seconds provides good balance. For minimal status bars (session name + time only), 15-60 seconds is sufficient.

Additional performance settings

extraConfig = ''
  # Enable focus events for vim/neovim
  set -g focus-events on
  
  # Aggressive resize (smart window sizing)
  setw -g aggressive-resize on
  
  # Disable visual activity monitoring
  setw -g monitor-activity off
  set -g visual-activity off
  
  # Disable visual bell
  set -g visual-bell off
'';

Performance anti-patterns to avoid

Don't: Use complex status lines with shell command substitutions that run frequently, set history-limit above 1 million, monitor activity on many windows simultaneously, run heavy compilation processes in visible panes (use separate windows), or create deeply nested tmux sessions (3+ levels).

Do: Keep status bars simple, use separate windows for background tasks, limit visible pane count to what you actually need, and leverage tmux-resurrect for session persistence instead of never-ending sessions that accumulate cruft.


Phase-Based Implementation Roadmap

Phase 1: Core architecture and foundation (Week 1)

Objective: Establish working declarative tmux configuration with basic functionality.

Tasks:

  1. Create modules/devshells/tmux.nix or modules/programs/tmux/default.nix following Kalilix patterns
  2. Configure home-manager programs.tmux with basic settings: terminal, historyLimit, baseIndex, keyMode, mouse, escapeTime, prefix
  3. Implement essential plugins: sensible, vim-tmux-navigator
  4. Set up human-friendly keybindings: Ctrl-a prefix, | and - splits, vi-mode copy bindings
  5. Configure OSC 52 clipboard support with set-clipboard on
  6. Test on primary development machine (macOS or Linux)

Success criteria: Can start tmux, split panes, navigate with Ctrl-hjkl, copy/paste with vi-mode, and reload configuration without errors.

Estimated time: 3-5 hours (configuration), 2-3 hours (testing and refinement)

Phase 2: Integration, theming, and cross-platform (Week 2)

Objective: Add Catppuccin theme, enhance clipboard integration, ensure cross-platform compatibility.

Tasks:

  1. Integrate Catppuccin plugin with Macchiato flavor and rounded window style
  2. Install Fira Code Nerd Font via Nix and verify rendering
  3. Implement platform-specific clipboard fallbacks (xsel for Linux, pbcopy for macOS)
  4. Configure neovim clipboard synchronization via OSC 52
  5. Test true color support: verify tmux-256color, terminal-overrides for RGB
  6. Create tmuxp YAML configuration for Kalilix project
  7. Enable tmuxp in home-manager and test tmuxp load . workflow
  8. Test on secondary platform (if primary is macOS, test Linux; vice versa)

Success criteria: Theme renders correctly with rounded separators, clipboard works bidirectionally between tmux/neovim/system, tmuxp loads project sessions successfully, works on both macOS and Linux.

Estimated time: 4-6 hours (configuration and integration), 3-4 hours (cross-platform testing)

Phase 3: Advanced features and optimization (Week 3)

Objective: Implement nested session support, SSH awareness, finalize performance tuning.

Tasks:

  1. Implement F12 nested session toggle with visual indicators
  2. Create SSH-aware configuration with remote.conf for automatic switching
  3. Set up SSH agent socket renewal (symlink pattern in ~/.ssh/rc)
  4. Configure environment variable refresh for long-running sessions
  5. Performance audit: verify escape-time 0, optimize history-limit, review status-interval
  6. Create documentation for team: README with setup instructions, troubleshooting guide
  7. Add example .tmuxp.yaml files for common project types
  8. Test nested session scenarios: local tmux → SSH → remote tmux
  9. Verify Ghostty compatibility specifically
  10. Create flake outputs for reusable packages

Success criteria: F12 toggle works correctly in nested scenarios, SSH detection applies appropriate config, no perceivable lag in vim mode switching, team members can clone and use configs immediately.

Estimated time: 5-7 hours (implementation), 3-4 hours (documentation and testing)

Post-implementation: Monitoring and iteration

Week 4 onwards: Collect feedback from team usage, identify edge cases in daily workflows, monitor performance under real workloads, and iterate on configurations based on actual usage patterns. Consider adding optional features like tmux-resurrect if team requests session persistence, additional status bar widgets based on team needs, or project-specific tmuxp templates for different development stacks.


Integration Strategies

Neovim integration deep-dive

Beyond vim-tmux-navigator, ensure tight integration between neovim and tmux through shared theme colors, unified clipboard registers, and consistent keybinding philosophy.

Neovim configuration additions:

-- Catppuccin theme matching tmux
require("catppuccin").setup({
  flavour = "macchiato",
  integrations = {
    tmux = true,
  },
})
vim.cmd.colorscheme "catppuccin-macchiato"

-- Vim-tmux-navigator plugin
require("lazy").setup({
  {
    "christoomey/vim-tmux-navigator",
    keys = {
      { "<C-h>", "<cmd>TmuxNavigateLeft<cr>" },
      { "<C-j>", "<cmd>TmuxNavigateDown<cr>" },
      { "<C-k>", "<cmd>TmuxNavigateUp<cr>" },
      { "<C-l>", "<cmd>TmuxNavigateRight<cr>" },
    },
  },
})

-- Disable tmux navigator when zoomed
vim.g.tmux_navigator_disable_when_zoomed = 1

-- OSC 52 clipboard
if os.getenv("TMUX") then
  vim.g.clipboard = {
    name = 'OSC 52',
    copy = {
      ['+'] = require('vim.ui.clipboard.osc52').copy('+'),
      ['*'] = require('vim.ui.clipboard.osc52').copy('*'),
    },
    paste = {
      ['+'] = require('vim.ui.clipboard.osc52').paste('+'),
      ['*'] = require('vim.ui.clipboard.osc52').paste('*'),
    },
  }
end

DevShell workflow patterns

For per-project development environments, integrate tmux with Nix devShells using direnv for automatic activation.

Pattern 1: Automatic tmuxp loading:

# .envrc in project root
use flake

# Start tmux session automatically
if [ -z "$TMUX" ] && [ -f .tmuxp.yaml ]; then
  tmuxp load . &
fi

Pattern 2: Custom tmux wrapper:

# In devShell
shellHook = ''
  alias dev-tmux="tmux -f ${projectTmuxConf}"
  echo "Use 'dev-tmux' to start project tmux session"
'';

Team collaboration guidelines

Git-committed files:

  • .tmuxp.yaml - Project session layout
  • README.md section documenting tmux setup
  • Optional: .tmux.conf.local.example for team-specific customizations

Per-user files (not committed):

  • ~/.config/tmux/local.conf - Individual overrides
  • Custom status bar widgets
  • Personal keybinding preferences

Documentation requirements:

  1. Prerequisites: Nix with flakes, home-manager version
  2. Quick start: nix develop, tmuxp load .
  3. Troubleshooting: Common issues and solutions
  4. Customization: How to override defaults without modifying shared config

Critical Technical Decisions

Plugin management: Nix-native vs TPM hybrid

Decision: Pure Nix, no TPM

Rationale: TPM requires runtime git operations, breaking hermetic builds. Nix plugin system provides identical functionality with reproducibility guarantees. The nixpkgs repository includes 57+ plugins covering 90% of use cases. For missing plugins, mkTmuxPlugin enables custom derivations.

Trade-off: Cannot use latest plugin commits without updating SHA256 hashes, but this aligns with Nix philosophy of reproducible builds. Use nix flake update workflow to update plugin versions.

Session management: tmuxp vs tmuxinator

Decision: tmuxp

Rationale: Python-based architecture avoids Ruby version conflicts, better home-manager integration, session freezing capability, config import from multiple formats, and alignment with Python-heavy modern workflows.

Trade-off: Slightly smaller community than tmuxinator (but both have excellent Nix support). Migration path exists for tmuxinator users via tmuxp import.

Navigation: vim-tmux-navigator vs tmux.nvim

Decision: vim-tmux-navigator

Rationale: 12+ years of development, largest community, best documentation, Vim and Neovim compatibility, and focused functionality (navigation only). tmux.nvim offers more features but has maintenance concerns and potential performance issues with clipboard sync.

Trade-off: No built-in pane resizing via hjkl. If needed, implement separate resize bindings with Alt-hjkl.

Clipboard: OSC 52 primary vs platform tools primary

Decision: OSC 52 primary, platform tools fallback

Rationale: OSC 52 works everywhere including SSH without X11 forwarding, nested tmux sessions, and modern terminal emulators. Platform tools (pbcopy, xsel) serve as supplementary mechanisms for local workflows.

Trade-off: Requires terminal emulator support. GNOME Terminal and XFCE Terminal users must rely on platform tools exclusively.

Theme: Catppuccin vs custom powerline

Decision: Catppuccin

Rationale: Official theme with extensive application support (200+ apps), native Nix packaging, active maintenance, four flavor options, and modern aesthetic. Powerline-style separators available via rounded window style.

Trade-off: Less customization than fully custom powerline themes, but 90% of users prefer standard themes for consistency across tools.


Technical Risks and Mitigations

Risk: Terminal emulator incompatibility

Scenario: OSC 52 clipboard doesn't work on user's terminal.

Mitigation: Implement automatic fallback detection to platform-specific tools. Add troubleshooting documentation listing supported terminals. For unsupported terminals, configuration gracefully degrades to xsel/pbcopy without breaking.

Risk: SSH agent socket staleness

Scenario: Long-running tmux sessions lose access to SSH keys after reconnection.

Mitigation: Implement symlink pattern in ~/.ssh/rc for automatic socket renewal. Document shell hook pattern for manual refresh. Provide prefix+$ binding to refresh environment variables.

Risk: Nested session confusion

Scenario: Users don't understand F12 toggle, try to control inner session with outer keybindings.

Mitigation: Prominent visual indicator in status bar showing "OFF" when toggled. Documentation with clear explanation and diagrams. Consider adding brief message on F12 press: "Outer tmux disabled - press F12 to restore".

Risk: Plugin version conflicts

Scenario: Catppuccin theme breaks after nixpkgs update.

Mitigation: Pin nixpkgs input in flake.lock for stability. Test updates in separate branch before merging to main. Document known-good nixpkgs commit hashes. Consider vendoring critical plugins as custom derivations.

Risk: Team adoption resistance

Scenario: Team members prefer existing tmux configurations, resist switching to declarative approach.

Mitigation: Hybrid configuration pattern allowing .tmux.conf.local for personal overrides. Migration guide for importing existing configs. Emphasize benefits: reproducibility, team consistency, zero-setup onboarding. Make adoption gradual and optional initially.


Troubleshooting Guide

Clipboard not working

Symptoms: Copy in tmux doesn't reach system clipboard, or paste doesn't work.

Solutions:

  1. Verify OSC 52 support: Check terminal emulator settings, enable clipboard access in iTerm2/Kitty
  2. Test with echo: echo -e "\e]52;c;$(echo -n "test" | base64)\a" should copy "test" to clipboard
  3. Check tmux config: Verify set -s set-clipboard on is present
  4. Platform tools: Ensure xsel/wl-clipboard (Linux) or pbcopy (macOS) are installed
  5. Test manually: tmux save-buffer - | xsel -i --clipboard should work if platform tool is configured correctly

Vim navigation not working

Symptoms: Ctrl-hjkl doesn't navigate between vim and tmux panes.

Solutions:

  1. Verify plugin installation: Check both tmux (tmux list-keys | grep tmux-navigator) and neovim (:echo g:loaded_tmux_navigator)
  2. Check process detection: Run ps -o state= -o comm= -t '#{pane_tty}' in tmux pane to verify vim is detected
  3. Update patterns: Add your editor to detection regex if using non-standard vim (e.g., nvim-qt)
  4. Clear keybinding conflicts: Ensure no other plugins are capturing Ctrl-hjkl

Escape key delay in vim

Symptoms: Slow mode switching, visible lag pressing ESC.

Solutions:

  1. Verify escape-time: tmux show-options -g | grep escape-time should show 0 or small value (10-20)
  2. Apply setting: Run tmux set-option -g escape-time 0 then restart tmux server with tmux kill-server
  3. Check vim: Ensure vim has set ttimeoutlen=0 or equivalent

Theme not rendering correctly

Symptoms: Boxes or question marks instead of icons, incorrect colors, missing separators.

Solutions:

  1. Verify Nerd Font: Check terminal font is "FiraCode Nerd Font" or similar patched font
  2. Test true color: Run color test script, verify RGB support with tmux info | grep RGB
  3. Check terminal-overrides: Ensure set -ga terminal-overrides ",xterm-256color:RGB" is present
  4. Restart tmux server: Theme changes require tmux kill-server not just reload
  5. Verify Catppuccin config: Check @catppuccin_flavour is set correctly

F12 toggle not working

Symptoms: Pressing F12 doesn't disable outer tmux session in nested scenarios.

Solutions:

  1. Verify binding: tmux list-keys -T root | grep F12 should show binding
  2. Check terminal: Some terminals capture F12 before sending to tmux, try F11 instead
  3. Test manually: Run tmux set-option -g prefix None to verify behavior
  4. Apply configuration: Ensure nested-session.conf is sourced in main config

Ghostty terminal issues

Symptoms: Colors wrong, clipboard not working, or terminfo errors.

Solutions:

  1. Set TERM correctly: Use term = "xterm-256color" in Ghostty config
  2. Enable clipboard: Check Ghostty settings for clipboard access permission
  3. Terminal features: Use set -as terminal-features ",xterm*:RGB" for Ghostty
  4. SSH issues: Copy terminfo to remote with infocmp xterm-ghostty | ssh remote "tic -x -"

Future Enhancements and Research Areas

Advanced integration possibilities

Tmux control mode: Research using tmux's -CC flag for programmatic control, enabling custom UIs and advanced automation scripts.

Kitty Graphics Protocol: Investigate rendering images in tmux panes using Ghostty/Kitty's graphics protocol, useful for ML/data science workflows.

AI pair programming integration: Explore tmux layouts optimized for AI assistant workflows (separate pane for context, command suggestions).

Performance optimization opportunities

Lazy plugin loading: Research conditional plugin loading based on detected context (only load SSH-aware plugins when SSH_CLIENT is set).

Status bar caching: Investigate caching expensive status bar computations (CPU usage, git branch) with smarter invalidation.

Pane rendering optimization: Research tmux 3.4+ rendering improvements and aggressive-resize enhancements.

Session management evolution

tmux-navi integration: Explore fuzzy-finding session/window/pane navigation similar to telescope.nvim.

Automated session creation: Research hooks to automatically create project-specific tmux sessions when entering directories with .tmuxp.yaml.

Session templates: Build library of tmuxp templates for common development scenarios (monorepo, microservices, data science, etc.).

Monitoring and analytics

Usage analytics: Implement optional telemetry to understand which features are actually used (inform future decisions).

Performance profiling: Create benchmarks for different configuration approaches to quantify performance impacts.

User experience research: Conduct user studies to identify friction points in current workflows.


Conclusion and Recommendations

This specification provides production-ready architecture for implementing tmux in the Kalilix project using declarative Nix flakes and home-manager. The recommended configuration—Ctrl-a prefix, vim-tmux-navigator, Catppuccin Macchiato, tmuxp session management, OSC 52 clipboard, and F12 nested session toggle—represents battle-tested patterns from top configurations analyzed across 20,000+ combined GitHub stars.

Critical success factors:

  1. Start minimal: Phase 1 foundation before advanced features
  2. Test incrementally: Verify each feature before adding next
  3. Document thoroughly: Team adoption depends on clear documentation
  4. Embrace constraints: Declarative Nix patterns over imperative flexibility
  5. Iterate based on usage: Real workflows reveal optimization opportunities

Next immediate action: Create modules/devshells/tmux.nix in Kalilix repository, implement Phase 1 core configuration, and test on primary development machine. Success here validates approach for Phases 2 and 3.

The architecture is viable for immediate implementation, technically sound based on extensive research, and positions Kalilix for collaborative refinement. This specification should serve as both implementation guide and reference documentation for the team.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment