Skip to content

Instantly share code, notes, and snippets.

@thimslugga
Last active October 19, 2026 13:15
Show Gist options
  • Select an option

  • Save thimslugga/c838a6f632201df94edf8968bd28b990 to your computer and use it in GitHub Desktop.

Select an option

Save thimslugga/c838a6f632201df94edf8968bd28b990 to your computer and use it in GitHub Desktop.
Agent Teams Setup
-- wezterm.lua -- config optimized for Claude Code
-- Place at: ~/.wezterm.lua
--
-- NOTE: Like zellij, WezTerm does not yet have native Claude Code
-- agent team split-pane support (only tmux and iTerm2 do).
-- Agent teams will fall back to in-process mode. However, WezTerm's
-- CLI (wezterm cli split-pane, send-text, etc.) makes manual
-- orchestration straightforward. See the companion launcher script.
local wezterm = require("wezterm")
local act = wezterm.action
local config = wezterm.config_builder()
-- -------------------------------------------------------------------
-- OS detection
-- -------------------------------------------------------------------
local is_mac = wezterm.target_triple:find("darwin") ~= nil
local is_linux = wezterm.target_triple:find("linux") ~= nil
-- -------------------------------------------------------------------
-- Core settings for Claude Code
-- -------------------------------------------------------------------
-- Large scrollback. Claude Code sessions produce a lot of output,
-- especially during diffs and agent work. Default is 3500.
config.scrollback_lines = 250000
-- Enable the kitty keyboard protocol. This is what makes Shift+Enter,
-- Ctrl+Shift combos, and other modifier sequences work correctly in
-- Claude Code without any hacks. WezTerm supports this natively.
config.enable_kitty_keyboard = true
-- Use vim as the scrollback pager. Ctrl+Shift+H opens the full
-- scrollback in your editor for searching and copying.
config.scrollback_pager = "vim -c 'set ft=man' -"
-- Clipboard integration -- just works on all platforms.
config.canonicalize_pasted_newlines = "None"
-- Enable agent teams via environment variable.
config.set_environment_variables = {
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1",
-- WezTerm sets TERM_PROGRAM=WezTerm automatically.
}
-- -------------------------------------------------------------------
-- Appearance
-- -------------------------------------------------------------------
-- Color scheme. WezTerm ships 700+ built-in schemes.
-- Uncomment your preferred one:
-- config.color_scheme = "Catppuccin Mocha"
-- config.color_scheme = "Gruvbox dark, hard (base16)"
-- config.color_scheme = "nord"
-- config.color_scheme = "Tokyo Night"
config.color_scheme = "Builtin Tango Dark"
-- Font. Change to whatever you have installed.
config.font_size = 13.0
-- config.font = wezterm.font("JetBrains Mono")
-- config.font = wezterm.font("Fira Code")
-- Window
config.window_decorations = "RESIZE"
config.window_padding = { left = 4, right = 4, top = 4, bottom = 4 }
config.adjust_window_size_when_changing_font_size = false
-- Tab bar
config.use_fancy_tab_bar = false
config.tab_bar_at_bottom = true
config.hide_tab_bar_if_only_one_tab = false
config.show_new_tab_button_in_tab_bar = false
-- Show which pane is focused with a slightly different inactive color
config.inactive_pane_hsb = {
saturation = 0.8,
brightness = 0.7,
}
-- -------------------------------------------------------------------
-- Leader key (Ctrl+Space, same as your tmux secondary prefix)
-- -------------------------------------------------------------------
-- This gives you a tmux-like prefix key workflow. Press Ctrl+Space
-- then a key within 1 second. The leader key does not interfere
-- with Claude Code's own keybindings since it's a chord, not a
-- single modifier.
config.leader = {
key = "Space",
mods = "CTRL",
timeout_milliseconds = 1000,
}
-- -------------------------------------------------------------------
-- Key bindings
-- -------------------------------------------------------------------
config.keys = {
-- == Pane splits (leader + | or -) ==
{
key = "|",
mods = "LEADER|SHIFT",
action = act.SplitPane({
direction = "Right",
size = { Percent = 50 },
}),
},
{
key = "-",
mods = "LEADER",
action = act.SplitPane({
direction = "Down",
size = { Percent = 50 },
}),
},
-- == Vim-style pane navigation (leader + h/j/k/l) ==
{ key = "h", mods = "LEADER", action = act.ActivatePaneDirection("Left") },
{ key = "j", mods = "LEADER", action = act.ActivatePaneDirection("Down") },
{ key = "k", mods = "LEADER", action = act.ActivatePaneDirection("Up") },
{ key = "l", mods = "LEADER", action = act.ActivatePaneDirection("Right") },
-- == Pane resize (leader + H/J/K/L) ==
{ key = "H", mods = "LEADER|SHIFT", action = act.AdjustPaneSize({ "Left", 5 }) },
{ key = "J", mods = "LEADER|SHIFT", action = act.AdjustPaneSize({ "Down", 3 }) },
{ key = "K", mods = "LEADER|SHIFT", action = act.AdjustPaneSize({ "Up", 3 }) },
{ key = "L", mods = "LEADER|SHIFT", action = act.AdjustPaneSize({ "Right", 5 }) },
-- == Pane zoom (leader + z) -- like tmux prefix+z ==
{ key = "z", mods = "LEADER", action = act.TogglePaneZoomState },
-- == Close pane (leader + x) ==
{ key = "x", mods = "LEADER", action = act.CloseCurrentPane({ confirm = true }) },
-- == Tab management ==
{ key = "c", mods = "LEADER", action = act.SpawnTab("CurrentPaneDomain") },
{ key = "n", mods = "LEADER", action = act.ActivateTabRelative(1) },
{ key = "p", mods = "LEADER", action = act.ActivateTabRelative(-1) },
-- Direct tab access (leader + 1-9)
{ key = "1", mods = "LEADER", action = act.ActivateTab(0) },
{ key = "2", mods = "LEADER", action = act.ActivateTab(1) },
{ key = "3", mods = "LEADER", action = act.ActivateTab(2) },
{ key = "4", mods = "LEADER", action = act.ActivateTab(3) },
{ key = "5", mods = "LEADER", action = act.ActivateTab(4) },
-- == Alt-arrow pane navigation (no leader needed) ==
{ key = "LeftArrow", mods = "ALT", action = act.ActivatePaneDirection("Left") },
{ key = "RightArrow", mods = "ALT", action = act.ActivatePaneDirection("Right") },
{ key = "UpArrow", mods = "ALT", action = act.ActivatePaneDirection("Up") },
{ key = "DownArrow", mods = "ALT", action = act.ActivatePaneDirection("Down") },
-- == Reload config (leader + r) ==
{
key = "r",
mods = "LEADER",
action = act.ReloadConfiguration,
},
-- == Copy mode (leader + [) -- like tmux ==
{ key = "[", mods = "LEADER", action = act.ActivateCopyMode },
-- == Open scrollback in vim (leader + e) ==
-- Captures the entire scrollback + visible area and opens in vim.
{
key = "e",
mods = "LEADER",
action = act.EmitEvent("open-scrollback-in-vim"),
},
-- == Quick Claude Code pane (leader + y) ==
-- Splits right and launches claude in the new pane.
-- Uses the two-step approach (split then send-text) to preserve
-- your shell's PATH, since wezterm cli split-pane -- claude
-- bypasses .bashrc/.zshrc and loses PATH entries.
{
key = "y",
mods = "LEADER",
action = act.SplitPane({
direction = "Right",
size = { Percent = 60 },
command = {
args = { is_mac and "/bin/zsh" or "/bin/bash", "-ilc", "claude" },
},
}),
},
-- == Agent team layout: 3-way split (leader + a) ==
-- Creates a lead pane on the left (50%) and two agent panes
-- stacked on the right.
{
key = "a",
mods = "LEADER",
action = act.EmitEvent("spawn-agent-layout"),
},
-- == Workspace switching (leader + w) ==
-- WezTerm workspaces are the equivalent of tmux sessions.
-- Each workspace is an independent group of windows/tabs/panes.
{
key = "w",
mods = "LEADER",
action = act.ShowLauncherArgs({ flags = "FUZZY|WORKSPACES" }),
},
-- == New workspace (leader + W) ==
{
key = "W",
mods = "LEADER|SHIFT",
action = act.PromptInputLine({
description = "Enter name for new workspace",
action = wezterm.action_callback(function(window, pane, line)
if line then
window:perform_action(
act.SwitchToWorkspace({ name = line }),
pane
)
end
end),
}),
},
-- == Command palette (Ctrl+Shift+P) ==
-- Already bound by default, listed here for reference.
-- { key = "P", mods = "CTRL|SHIFT", action = act.ActivateCommandPalette },
}
-- -------------------------------------------------------------------
-- Custom events
-- -------------------------------------------------------------------
-- Open full scrollback in vim
wezterm.on("open-scrollback-in-vim", function(window, pane)
local text = pane:get_lines_as_text(config.scrollback_lines)
local tmpfile = os.tmpname()
local f = io.open(tmpfile, "w+")
if f then
f:write(text)
f:close()
window:perform_action(
act.SpawnCommandInNewTab({
args = { "vim", "-c", "normal G", tmpfile },
}),
pane
)
-- Clean up after vim exits (wezterm keeps the file reference)
wezterm.sleep_ms(1000)
end
end)
-- Spawn a 3-pane agent team layout in the current tab
wezterm.on("spawn-agent-layout", function(window, pane)
local shell = is_mac and "/bin/zsh" or "/bin/bash"
-- Split right for agent-1 (takes 50% of the tab)
local agent1 = pane:split({
direction = "Right",
size = 0.5,
args = { shell, "-ilc", "claude" },
})
-- Split agent-1 pane downward for agent-2
if agent1 then
agent1:split({
direction = "Bottom",
size = 0.5,
args = { shell, "-ilc", "claude" },
})
end
-- The original pane (left side) is your lead / shell.
-- Type 'claude' in it to make it the team lead, or use it
-- as a shell for git/tests/monitoring.
end)
-- -------------------------------------------------------------------
-- Tab title: show running process name
-- -------------------------------------------------------------------
-- This helps identify which tab has which Claude Code agent.
wezterm.on("format-tab-title", function(tab)
local pane = tab.active_pane
local title = pane.title
-- Truncate long titles
if #title > 24 then
title = title:sub(1, 21) .. "..."
end
local index = tab.tab_index + 1
return string.format(" %d: %s ", index, title)
end)
-- -------------------------------------------------------------------
-- Right status: show workspace name and working directory
-- -------------------------------------------------------------------
wezterm.on("update-status", function(window, pane)
local workspace = window:active_workspace()
local cwd = ""
local cwd_uri = pane:get_current_working_dir()
if cwd_uri then
cwd = cwd_uri.file_path or ""
-- Shorten home directory
local home = os.getenv("HOME") or ""
if home ~= "" and cwd:sub(1, #home) == home then
cwd = "~" .. cwd:sub(#home + 1)
end
end
window:set_right_status(wezterm.format({
{ Foreground = { Color = "#6a9955" } },
{ Text = " " .. workspace .. " " },
{ Foreground = { Color = "#569cd6" } },
{ Text = cwd .. " " },
}))
end)
return config

Claude Code Agents

Overview

mkdir -p ~/workspace/myproject

cd ~/workspace/myproject

claude-agents.sh                       # start or reattach a session
claude-agents.sh spawn researcher      # add a pane running claude
claude-agents.sh spawn implementer down # split downward
claude-agents.sh spawn-float debugger  # floating popup pane
claude-agents.sh list                  # see all claude sessions
#!/usr/bin/env bash
set -euo pipefail
# cc-agents.sh - Spawn Claude Code Agents in Zellij Panes
#
# Usage:
# cc-agents.sh # start a new session with the layout
# cc-agents.sh spawn researcher # spawn a named agent pane (right split)
# cc-agents.sh spawn-float debugger # spawn a named floating agent pane
# cc-agents.sh spawn-tab backend # spawn agent in a new tab
# cc-agents.sh attach # attach to existing session
# cc-agents.sh list # list running claude sessions
#
# Requires: zellij, claude (Claude Code CLI)
SESSION_PREFIX="claude"
LAYOUT_NAME="cc-agents"
# Derive a session name from the current directory.
# Same idea as the tmux config: one session per project directory.
function dir_hash() {
echo -n "$PWD" | md5sum 2>/dev/null | cut -c1-8 ||
echo -n "$PWD" | md5 2>/dev/null | cut -c1-8 ||
echo -n "$PWD" | cksum | cut -d' ' -f1
}
SESSION_NAME="${SESSION_PREFIX}-$(dir_hash)"
function cmd_start() {
# If session already exists, attach to it
if zellij list-sessions 2>/dev/null | grep -q "^${SESSION_NAME}"; then
echo "Attach to an existing session: ${SESSION_NAME}"
zellij attach "$SESSION_NAME"
else
echo "Start new session: ${SESSION_NAME}"
echo "Current working directory: ${PWD}"
zellij --session "$SESSION_NAME" --layout "$LAYOUT_NAME"
fi
}
function cmd_spawn() {
local name="${1:-agent}"
local direction="${2:-right}"
if [ -z "$ZELLIJ" ]; then
echo "ERROR: not inside a zellij session." >&2
echo "Run 'cc-agents.sh' first to start a session." >&2
exit 1
fi
echo "Spawning agent pane: ${name} (direction: ${direction})"
zellij action new-pane --direction "$direction" --name "$name" -- claude
}
function cmd_spawn_float() {
local name="${1:-agent}"
if [ -z "$ZELLIJ" ]; then
echo "Error: not inside a zellij session." >&2
exit 1
fi
echo "INFO: Spawning floating agent pane: ${name}"
zellij action new-pane --floating --name "$name" -- claude
}
function cmd_spawn_tab() {
local name="${1:-agent}"
if [ -z "$ZELLIJ" ]; then
echo "ERROR: Not inside a zellij session." >&2
exit 1
fi
echo "INFO: Spawning agent in new tab: ${name}"
zellij action new-tab --name "$name" --layout-dir "$HOME/.config/zellij/layouts"
# Give the tab a moment to initialize, then run claude in it
sleep 0.3
zellij action write-chars "claude"
zellij action write 10 # send Enter (newline char)
}
function cmd_attach() {
if zellij list-sessions 2>/dev/null | grep -q "^${SESSION_NAME}"; then
zellij attach "$SESSION_NAME"
else
echo "No session found for this directory (${SESSION_NAME})." >&2
echo "Run 'cc-agents.sh' to start one." >&2
exit 1
fi
}
function cmd_list() {
echo "Claude Code Zellij Managed Sessions:"
zellij list-sessions 2>/dev/null | grep "^${SESSION_PREFIX}" || echo " (none)"
}
function cmd_help() {
cat <<'EOF'
cc-agents.sh - Manage Claude Code agent panes in zellij
Commands:
(no args) Start or attach to a session for the current directory
spawn <name> [dir] Spawn a named agent pane (split right by default)
dir: right, down
spawn-float <name> Spawn a named floating agent pane
spawn-tab <name> Spawn an agent in a new tab
attach Attach to the session for the current directory
list List running claude sessions
help Show this message
Examples:
cd ~/myproject
cc-agents.sh # start session
cc-agents.sh spawn researcher # add a researcher pane
cc-agents.sh spawn implementer down # add an implementer below
cc-agents.sh spawn-float debugger # floating debugger pane
EOF
}
# ---------------------------------------------------------------
# Main dispatch
# ---------------------------------------------------------------
case "${1:-}" in
spawn) cmd_spawn "${2:-}" "${3:-right}" ;;
spawn-float) cmd_spawn_float "${2:-}" ;;
spawn-tab) cmd_spawn_tab "${2:-}" ;;
attach) cmd_attach ;;
list) cmd_list ;;
help|--help|-h) cmd_help ;;
"") cmd_start ;;
*)
echo "Unknown command: $1" >&2
cmd_help >&2
exit 1
;;
esac
// cc_agents.kdl -- layout for Claude Code agent orchestration
//
// Place at: ~/.config/zellij/layouts/cc_agents.kdl
//
// Default tab: a single Claude Code lead pane + a shell pane.
// Use the launcher script or `zellij action new-pane` to spawn
// additional agent panes as needed.
//
// Usage:
// zellij --layout cc-agents
// zellij --layout cc-agents --session my-project
layout {
// ---------------------------------------------------------------
// Tab 1: Lead agent + Shell
// ---------------------------------------------------------------
// The lead pane gets 70% width. The right side has a shell
// for running git, tests, watching logs, etc.
tab name="lead" focus=true {
pane split_direction="vertical" {
pane size="70%" name="lead" focus=true
pane size="30%" name="shell"
}
}
// ---------------------------------------------------------------
// Tab 2: Agent workspace, pre-split for 2-3 agents
// ---------------------------------------------------------------
// Start with two empty panes side by side. Spawn claude in each
// one manually, or use the launcher script. The swap_tiled_layout
// below will auto-rearrange as you add more panes.
tab name="agents" {
pane split_direction="vertical" {
pane name="agent-1"
pane name="agent-2"
}
}
// ---------------------------------------------------------------
// Tab 3: Monitor for logs and server output
// ---------------------------------------------------------------
tab name="monitor" {
pane split_direction="vertical" {
pane name="logs"
pane split_direction="horizontal" {
pane name="server"
pane name="tests"
}
}
}
// ---------------------------------------------------------------
// Tab template (used for any new tabs created at runtime)
// ---------------------------------------------------------------
default_tab_template {
pane size=1 borderless=true {
plugin location="zellij:tab-bar"
}
children
pane size=1 borderless=true {
plugin location="zellij:status-bar"
}
}
// ---------------------------------------------------------------
// Swap layouts: auto-rearrange tiled panes as count changes
// ---------------------------------------------------------------
// When you add or remove panes, zellij picks the best layout
// from these candidates. This keeps things readable as you
// spawn more agent panes.
swap_tiled_layout name="lead-right" {
// 1 pane: just fullscreen it
tab {
pane
}
// 2 panes: 70/30 vertical split (lead on left)
tab {
pane split_direction="vertical" {
pane size="70%"
pane size="30%"
}
}
// 3 panes: lead on left, two stacked on right
tab {
pane split_direction="vertical" {
pane size="60%"
pane split_direction="horizontal" size="40%" {
pane
pane
}
}
}
// 4 panes: 2x2 grid
tab {
pane split_direction="vertical" {
pane split_direction="horizontal" {
pane
pane
}
pane split_direction="horizontal" {
pane
pane
}
}
}
// 5+ panes: lead on left, agents stacked on right
tab {
pane split_direction="vertical" {
pane size="50%"
pane split_direction="horizontal" size="50%" stacked=true {
pane
pane
pane
pane
}
}
}
}
swap_tiled_layout name="equal-grid" {
tab {
pane
}
tab {
pane split_direction="vertical" {
pane
pane
}
}
tab {
pane split_direction="vertical" {
pane
pane
pane
}
}
tab {
pane split_direction="vertical" {
pane split_direction="horizontal" {
pane
pane
}
pane split_direction="horizontal" {
pane
pane
}
}
}
}
}
#!/bin/bash
set -euo pipefail
# claude-wezterm.sh -- spawn Claude Code agent panes in WezTerm
#
# Place somewhere in your $PATH (e.g. ~/bin/claude-wezterm.sh)
# chmod +x claude-wezterm.sh
#
# Usage:
# claude-wezterm.sh # start claude in a new workspace
# claude-wezterm.sh spawn researcher # spawn a named agent pane (right)
# claude-wezterm.sh spawn implementer down # spawn agent below
# claude-wezterm.sh team 3 # spawn N agent panes at once
# claude-wezterm.sh list # list panes
#
# IMPORTANT: wezterm cli split-pane -- claude bypasses your shell
# startup files (.bashrc, .zshrc), which means PATH entries from
# nvm, homebrew, cargo, etc. are lost and claude won't be found.
# This script uses the two-step approach: split first, then
# send-text to type the command into the new shell.
#
# Requires: wezterm (running), claude (Claude Code CLI)
# Spawn a pane and run a command in it via send-text.
# Returns the new pane ID.
spawn_pane() {
local direction="${1:-right}"
local name="${2:-agent}"
local pane_id
# Map friendly names to wezterm directions
case "$direction" in
right) direction="--right" ;;
down|bottom) direction="--bottom" ;;
left) direction="--left" ;;
up|top) direction="--top" ;;
*) direction="--right" ;;
esac
pane_id=$(wezterm cli split-pane "$direction")
if [ -z "$pane_id" ]; then
echo "Error: failed to create pane" >&2
return 1
fi
# Give the shell a moment to initialize
sleep 0.3
# Send the claude command into the new pane's shell.
# This preserves PATH from .bashrc/.zshrc since the pane
# started a login shell, not a bare process.
printf 'claude\n' | wezterm cli send-text --pane-id "$pane_id" --no-paste
echo "Spawned pane $pane_id ($name)"
echo "$pane_id"
}
cmd_start() {
# Create a new workspace named after the current directory
local workspace
workspace=$(basename "$PWD")
echo "Starting workspace: ${workspace}"
echo "Working directory: ${PWD}"
# Spawn a new window in a named workspace
wezterm cli spawn --workspace "$workspace" --cwd "$PWD" >/dev/null 2>&1 || true
# Launch claude in the current pane
printf 'claude\n' | wezterm cli send-text --no-paste
}
cmd_spawn() {
local name="${1:-agent}"
local direction="${2:-right}"
spawn_pane "$direction" "$name"
}
cmd_team() {
local count="${1:-3}"
local i
echo "Spawning ${count} agent panes..."
# First agent goes to the right
spawn_pane "right" "agent-1" >/dev/null
# Remaining agents stack below the first one.
# We need to focus the first agent pane before splitting.
for ((i = 2; i <= count; i++)); do
spawn_pane "down" "agent-${i}" >/dev/null
done
echo "Done. ${count} agent panes spawned."
echo "Use 'wezterm cli list' to see all panes."
}
cmd_list() {
echo "WezTerm panes:"
wezterm cli list
}
cmd_help() {
cat <<'EOF'
claude-wezterm.sh -- manage Claude Code agent panes in WezTerm
Commands:
(no args) Start claude in current pane
spawn <name> [dir] Spawn a named agent pane
dir: right (default), down, left, up
team <count> Spawn N agent panes at once (default: 3)
list List all WezTerm panes
help Show this message
Examples:
claude-wezterm.sh spawn researcher # right split
claude-wezterm.sh spawn implementer down # bottom split
claude-wezterm.sh team 4 # 4 agent panes
Notes:
WezTerm workspaces are the equivalent of tmux sessions.
Use Leader+w (Ctrl+Space, w) to switch between workspaces.
Use Leader+W to create a new named workspace.
EOF
}
case "${1:-}" in
spawn) cmd_spawn "${2:-}" "${3:-right}" ;;
team) cmd_team "${2:-3}" ;;
list) cmd_list ;;
help|--help|-h) cmd_help ;;
"") cmd_start ;;
*)
echo "Unknown command: $1" >&2
cmd_help >&2
exit 1
;;
esac
// config.kdl -- zellij config optimized for Claude Code
// Place at: ~/.config/zellij/config.kdl
//
// NOTE: Claude Code agent teams do not natively detect zellij yet
// (only tmux and iTerm2). Agent panes must be spawned manually
// via `zellij action` or using the included layout and launcher
// script. There is an open feature request for native support.
// -------------------------------------------------------------------
// Core options
// -------------------------------------------------------------------
// Kitty keyboard protocol is supported natively in zellij.
// This means Shift+Enter, Ctrl+Shift combos, etc. all work
// with Claude Code without any hacks. Enabled by default if
// your terminal supports it, but stated explicitly here.
support_kitty_keyboard_protocol true
// Session persistence across crashes and reboots.
// Sessions serialize to ~/.cache/zellij/ automatically.
session_serialization true
// Also capture pane viewport content when serializing.
// Lets you see where you left off after resurrection.
pane_viewport_serialization true
// Serialize scrollback so you don't lose Claude Code output
// history on resurrection. 10000 lines is a good balance
// between usefulness and disk/memory usage.
scrollback_lines_to_serialize 10000
// How often to serialize (seconds). Default 60 is fine for
// most use cases. Lower if you're paranoid about crashes.
// serialization_interval 60
// Scrollback buffer. Claude Code produces a lot of output
// during long sessions, especially with diffs and agent work.
scroll_buffer_size 50000
// Use vim for scrollback editing (prefix + e in scroll mode).
scrollback_editor "vim"
// Mouse support for scrolling and pane selection.
mouse_mode true
// Clipboard -- use system clipboard.
copy_clipboard "system"
copy_on_select true
// Don't show startup tips after initial setup.
show_startup_tips false
// When you open a new pane, use the current pane's working directory.
// This is the zellij equivalent of tmux's -c "#{pane_current_path}".
default_cwd ""
// Auto-layout: let zellij rearrange panes to fit predefined
// swap layouts when pane count changes. Useful when spawning
// multiple agent panes.
auto_layout true
// Use the claude-agents layout by default (place it in
// ~/.config/zellij/layouts/claude-agents.kdl).
// Comment this out if you want the stock default layout.
default_layout "claude-agents"
// -------------------------------------------------------------------
// Keybindings
// -------------------------------------------------------------------
//
// Zellij uses a modal system. By default:
// Ctrl+t = enter Tab mode
// Ctrl+p = enter Pane mode
// Ctrl+n = enter Resize mode
// Ctrl+s = enter Scroll mode
// Ctrl+o = enter Session mode
// Ctrl+h = enter Move mode
// Ctrl+q = quit
//
// These are sane defaults. The bindings below add shortcuts
// specifically useful for Claude Code agent orchestration
// without overriding the defaults.
keybinds {
// In normal mode, add Alt-key shortcuts for common actions
// so you don't have to enter a sub-mode first.
normal {
// Quick pane splits (like tmux | and -)
bind "Alt |" { NewPane "Right"; }
bind "Alt -" { NewPane "Down"; }
// Navigate between panes with Alt+arrow (no mode switch needed)
bind "Alt Left" { MoveFocusOrTab "Left"; }
bind "Alt Right" { MoveFocusOrTab "Right"; }
bind "Alt Up" { MoveFocus "Up"; }
bind "Alt Down" { MoveFocus "Down"; }
// Quick tab switching
bind "Alt 1" { GoToTab 1; }
bind "Alt 2" { GoToTab 2; }
bind "Alt 3" { GoToTab 3; }
bind "Alt 4" { GoToTab 4; }
bind "Alt 5" { GoToTab 5; }
// Floating pane toggle -- useful for a quick Claude Code
// popup without disrupting your layout.
bind "Alt f" { ToggleFloatingPanes; }
// New floating pane
bind "Alt F" { NewFloatingPane; }
// Toggle fullscreen on the focused pane (like tmux prefix+z)
bind "Alt z" { ToggleFocusFullscreen; }
// Tiled layout cycling (equivalent to the tmux layout bindings)
bind "Alt t" { NextSwapLayout; }
}
// Add vim-style navigation in pane mode
pane {
bind "h" { MoveFocus "Left"; }
bind "j" { MoveFocus "Down"; }
bind "k" { MoveFocus "Up"; }
bind "l" { MoveFocus "Right"; }
}
// Vim-style resize
resize {
bind "h" { Resize "Increase Left"; }
bind "j" { Resize "Increase Down"; }
bind "k" { Resize "Increase Up"; }
bind "l" { Resize "Increase Right"; }
bind "H" { Resize "Decrease Left"; }
bind "J" { Resize "Decrease Down"; }
bind "K" { Resize "Decrease Up"; }
bind "L" { Resize "Decrease Right"; }
}
}
// -------------------------------------------------------------------
// Theme
// -------------------------------------------------------------------
// Minimal dark theme. Zellij ships with several built-in themes.
// Uncomment one of the built-in options or define your own.
// theme "dracula"
// theme "gruvbox-dark"
// theme "nord"
// theme "catppuccin-mocha"
// theme "tokyo-night"
// Custom minimal theme:
themes {
claude-dark {
fg "#d4d4d4"
bg "#1e1e1e"
black "#1e1e1e"
red "#f44747"
green "#6a9955"
yellow "#d7ba7d"
blue "#569cd6"
magenta "#c586c0"
cyan "#4ec9b0"
white "#d4d4d4"
orange "#ce9178"
}
}
theme "claude-dark"
// -------------------------------------------------------------------
// Environment variables
// -------------------------------------------------------------------
// Enable Claude Code agent teams (experimental).
// This tells Claude Code the feature is available, even though
// zellij isn't auto-detected for split-pane spawning yet.
env {
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS "1"
}

tmux Cheatsheet

Tmux helps you emulate several shell sessions within the same terminal window.

My best use case for tmux:

  • connecting to remote terminal sessions via ssh. You can set up several shell sessions within tmux to work in, if you used several ssh-sessions instead, you may have to re-enter the passwords and wait for the authentication.
  • use in combination with Vim to make it more powerful
  • save and recover shell sessions for minimum setup overhead after coming to a new day of work
  • for using docker containers efficiently. By default docker run -ti $IMAGE_NAME bash only gives you one shell within the docker container. If you use tmux from within the container, you can launch different terminal apps / commands within the container.

Installing Tmux

Basics of Tmux

You can control aspects of Tmux with different Tmux commands, either by invoking Tmux

$ tmux OPTION

to enter Tmux with the OPTION / COMMAND.

After you have invoked tmux, if you have to enter extra command for them to be recognized as tmux. By default this prefix is CTRL b which I will represent by C-b or prefix.

C-b 

And if you see some commands:

$ tmux COMMAND

that people can invoke before entering a tmux session, you can invoke the same command from within a tmux session by typing:

C-b : COMMAND

Now we can start up a new tmux session

$ tmux new -s SESSION_NAME

and there should be a line showing that a tmux session is in place. A tmux session can be a group of terminal panes / windows which can be saved.

Detaching sessions by force:

C-b D  # then select one of the multiple sessions to detach

splittting your terminal window with Tmux

There are two ways you can do it, either by:

  • panes (these are workspaces that are created by splitting the area of your terminal app, each workspace runs its own shell process)
  • windows (aka tabs within tmux. Again each tab has its own shell process)

An example of how you can have a left vertical pane along side with two right horizontal panes: three Tmux panes laid out side by side

colors in tmux

for i in {0..255}; do
    printf "\x1b[38;5;${i}mcolour${i}\x1b[0m\n"
done

Splitting and moving around panes

C-b %    # splits terminal into left and right panes 
C-b "    # splits terminal into top and bottom panes 
C-b <UP / DOWN / LEFT / RIGHT ARROW>  # move around panes after some mapping
C-b o    # jump between different panes 
C-b C-o  # swap the location of two panes 
C-b x    # kill the current pane 
# M represents the `meta` key or `alt` on Mac keyboards 
C-b M-1             # vertical split, all panes same width, or stack panes vertically 
C-b M-2             # horizontal split, all panes same height
C-b M-3             # horizontal split, main pane on top,
                      other panes on bottom, vertically split, all same width
C-b M-4             # vertical split, main pane left,
                      other panes right, horizontally split, all same height
C-b M-5             # tile, new panes on bottom, same height before same width

# start scrolling up and down the tmux window using arrow keys
C-b [    # use C-c after done with scrolling

and within panes you can sub-split panes.

Search pane

C-b Esc /           # Search downward 
C-b Esc ?           # Search upward 

Convert pane from / to window

C-b !  # from pane to window
 # join window 1 to current pane stacked vertically (up / bottom)
C-b :join-pane -v -s :1 
# join window 1 to current pane stacked horizontally (left / right)
C-b :join-pane -h -s :1  
C-b meta-1  # switch from horizontally stacked to vertically stacked panes
C-b meta-2  # switch from vertically stacked to horizonally stacked 

Resizing pane

reference navigate to pane that you want to resize, if you want to resize a pane to expand vertically down, do

C-b resize-pane -D 20

there are 4 options for resizing pane:

-D means down
-U means up 
-L means left
-R means right

To add a window or tab that you can switch to...

C-b c   # add a new window / tab to current session
C-b window-number  # switch to a particular window / tab  

Tmux windows (tabs)

Windows (tabs)

C-b c  create window
C-b w  list windows
C-b n  next window
C-b p  previous window
C-b f  find window
C-b ,  name window
C-b &  kill window

Tmux sessions

C-b : attach -t SESSION_NAME  # attach / start a session 
C-b : detach -t SESSION_NAME  # detach session / temporarily stop 
C-b : kill-session -t SESSION_NAME  # killing a session 
C-b ( # switch to previous session
C-b ) # switch to next session
C-b : ls  # list current sessions 
C-b $  # rename current session

Saving / restoring tmux sessions with tmux resurrect

C-b C-s  # save
C-b C-r  # restore

The saved session files are at:

~/.tmux/resurrect/*.txt

Refresh the .tmux.conf file

C-b : source-file ~/.tmux.conf

You can fix all the key-bindings inside .tmux.conf and if you ever need the default keybindings in case you mess up, read this StackOverFlow post

Making sure that terminal colors are preserved

Making sure that iTerm2, Tmux and Vim works well with system clipboard

# clipboard settings
set-option -g default-command "reattach-to-user-namespace -l bash"
setw -g mode-keys vi

# Setup 'v' to begin selection as in Vim

bind-key -t vi-copy v begin-selection
bind-key -t vi-copy y copy-pipe "reattach-to-user-namespace pbcopy"

# Update default binding of `Enter` to also use copy-pipe
unbind -t vi-copy Enter
bind-key -t vi-copy Enter copy-pipe "reattach-to-user-namespace pbcopy"

# Bind ']' to use pbpaste
bind ] run "reattach-to-user-namespace pbpaste | tmux load-buffer - && tmux paste-buffer"

remote copy-and-paste with Vim and Tmux

make sure that your version of vim was compiled with support for clipboard which you can check when you type

$ vim --version | clipboard 
+clipboard       +xterm_clipboard

the + sign means that the feature is supported. And you can enable synchronizing between X11 clipboard as indicated by this post

Making sure that Tmux and Vim work well moving stuff around

Vim-tmux-navigator

showing all the key-bindings

$ tmux list-keys

solution to common problems

Possible issues with group permissions

if the directory permissions / group memberships are changed between the creation of a tmux session and the use of the directory, the group memberships will not be up to date.

Use

exec su -l $USER

to update the group permissions.

See superuser post

handling conflicts of different Tmux versions

$ tmux attach
protocol version mismatch (client 7, server 6)

$ pgrep tmux
3429
$ /proc/3429/exe attach

source StackOverFlow post

set tmux background bar color on a per-session basis

set -t SESSIONNAME status-bg blue

References are:

Custom bindings in tmux.conf (non-stock)

The stock bindings above are tmux defaults. These are the custom ones from the accompanying tmux.conf in this gist:

Keys Action
C-Space Secondary prefix (in addition to C-b)
prefix r Reload ~/.config/tmux/tmux.conf
prefix | / prefix - Split horizontal / vertical, new pane starts in the current directory (" and % unbound)
prefix c New window in the current directory
prefix h/j/k/l Move between panes (vim-style)
prefix H/J/K/L Resize panes (repeatable)
prefix t / prefix V / prefix B Tiled / main-vertical / main-horizontal layout
prefix y 80% popup with a per-directory persistent agent session. Dismiss and it keeps running in the background; prefix y again reattaches. The inner session uses F12 as its prefix so it never fights the outer C-b/C-Space.
prefix ` 80% shell popup in the current directory. Esc dismisses it; the underlying pane keeps running. Zero tokens: your own shell for ls, vim, git without leaving tmux.
v / C-v / y (in vi copy mode) Begin selection / rectangle toggle / yank to system clipboard (via tmux-yank)

Notes:

  • Session names for the y popup are derived from the working directory with cksum, which is POSIX and works on both macOS and Linux (md5sum is Linux-only).
  • aggressive-resize on keeps windows full-size unless a client is actually viewing them; useful when attached from more than one device.
  • detach-on-destroy off switches to the most recent remaining session when one is destroyed instead of detaching you.
# tmux.conf
# Copy to ~/.config/tmux/tmux.conf or ~/.tmux.conf
# git clone https://github.com/tmux-plugins/tpm ~/.config/tmux/plugins/tpm
# and then reload: tmux source-file ~/.config/tmux/tmux.conf
# -------------------------------------------------------------------
# General
# -------------------------------------------------------------------
# True color support
set -g default-terminal "tmux-256color"
set -ag terminal-overrides ",xterm-256color:RGB"
# Use C-a instead (screen-like behavior)
#set-option -g prefix C-a
#unbind C-b
#bind C-a send-prefix
# Set Ctrl-Space as a secondary prefix key (in addition to the default Ctrl-b)
set -g prefix2 C-Space
bind C-Space send-prefix
# Reload config
#bind r source-file ~/.tmux.conf \; display-message "Config reloaded"
bind r source-file ~/.config/tmux/tmux.conf \; display-message "tmux config reloaded."
# Pass escape sequences (notifications, progress bar, clipboard)
# through to the outer terminal. Without this, tmux eats them.
set -g allow-passthrough on
# OSC 52 clipboard -- works over SSH and nested sessions.
set -g set-clipboard on
# Extended key sequences
set -g extended-keys on
set -as terminal-features 'xterm*:extkeys'
# Kill the 500ms escape delay
set -sg escape-time 0
# Default 2000 lines is not enough
set -g history-limit 250000
# Vi mode for copy mode and status keys.
setw -g mode-keys vi
set -g status-keys vi
# Mouse for scrolling and pane selection.
set -g mouse on
# Focus events for cursor management
set -g focus-events on
# Highlight windows in the status bar when there is activity in them
setw -g monitor-activity on
# Show a status bar message when activity is detected in a non-active window
set -g visual-activity on
# Start window/pane numbering at 1 (0 is awkward to reach)
set -g base-index 1
setw -g pane-base-index 1
# Renumber windows when one is closed.
set -g renumber-windows on
# Only shrink a window for clients actually looking at it (matters with
# a phone/laptop attached to the same session).
set -g aggressive-resize on
# Destroying the last session switches to the most recent remaining one
# instead of detaching you entirely (nicer with popup-spawned sessions).
set -g detach-on-destroy off
set-window-option -g automatic-rename on
set-option -g set-titles on
# -------------------------------------------------------------------
# tmux plugins (https://github.com/tmux-plugins/tpm)
# -------------------------------------------------------------------
# tmux plugins
set -g @plugin 'tmux-plugins/tpm'
# tmux-sensible: a set of reasonable default tmux settings everyone can agree on
#set -g @plugin 'tmux-plugins/tmux-sensible'
# -------------------------------------------------------------------
# tmux-resurrect and tmux-continuum
# -------------------------------------------------------------------
# tmux-resurrect: save and restore tmux sessions across system restarts
#set -g @plugin 'tmux-plugins/tmux-resurrect'
# tmux-continuum: continuously save sessions and auto-restore on tmux start
#set -g @plugin 'tmux-plugins/tmux-continuum'
# Automatically restore the last saved session when tmux starts
#set -g @continuum-restore 'on'
# Save sessions every 15 minutes
#set -g @continuum-save-interval '15'
# Also save the contents of each pane when capturing sessions
#set -g @resurrect-capture-pane-contents 'on'
# Use the Neovim session strategy when restoring (restores Neovim sessions too)
#set -g @resurrect-strategy-nvim 'session'
# -------------------------------------------------------------------
# Splits and navigation
# -------------------------------------------------------------------
# Intuitive split bindings, open in current path.
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
unbind '"'
unbind %
# New windows also inherit the current path.
bind c new-window -c "#{pane_current_path}"
# Vim-style pane navigation.
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
# Pane resize (repeatable).
bind -r H resize-pane -L 5
bind -r J resize-pane -D 3
bind -r K resize-pane -U 3
bind -r L resize-pane -R 5
# -------------------------------------------------------------------
# Copy mode (vi)
# -------------------------------------------------------------------
# tmux-yank: improved clipboard integration for copy mode
set -g @plugin 'tmux-plugins/tmux-yank'
# After yanking, exit copy mode and cancel the selection (default behavior)
set -g @yank_action 'copy-pipe-and-cancel'
# Copy selected text to the system clipboard using xsel (for non-vi copy mode)
#bind -T copy-mode C-c send -X copy-pipe-no-clear "xsel -i --clipboard"
# Copy selected text to the system clipboard using xsel (for vi copy mode)
#bind -T copy-mode-vi C-c send -X copy-pipe-no-clear "xsel -i --clipboard"
bind -T copy-mode-vi v send-keys -X begin-selection
bind -T copy-mode-vi C-v send-keys -X rectangle-toggle
bind -T copy-mode-vi y send-keys -X copy-selection-and-cancel
# -------------------------------------------------------------------
# Agent team layouts
# -------------------------------------------------------------------
# Quick layouts when spawning multiple agent panes.
bind t select-layout tiled # equal space for all
bind V select-layout main-vertical # lead left, agents stacked right
bind B select-layout main-horizontal # lead top, agents below
setw -g main-pane-width 50%
setw -g main-pane-height 50%
# -------------------------------------------------------------------
# Claude Code popup (nested session per directory)
# -------------------------------------------------------------------
# prefix + y: toggle an 80% popup running Claude Code.
# Each working directory gets its own persistent session.
# Dismiss the popup and the session keeps running in the background.
# The inner session uses F12 as its prefix so it never fights the outer
# C-b / C-Space, and no editing keys (C-a, etc.) are stolen from the app.
# cksum instead of md5sum: POSIX, present on macOS and Linux.
bind y run-shell '\
SESSION="claude-$(echo #{pane_current_path} | cksum | cut -d\" \" -f1)"; \
if ! tmux has-session -t "$SESSION" 2>/dev/null; then \
tmux new-session -d -s "$SESSION" -c "#{pane_current_path}" "claude"; \
tmux set-option -t "$SESSION" prefix F12; \
fi; \
tmux display-popup -w80% -h80% -E "tmux attach-session -t $SESSION"'
# -------------------------------------------------------------------
# Shell popup
# -------------------------------------------------------------------
# prefix + `: 80% shell popup in the current directory. Esc dismisses it;
# the underlying pane (agent or otherwise) keeps running underneath.
# Your own shell, zero tokens: ls, vim, git, whatever, without leaving tmux.
bind ` display-popup -w80% -h80% -d "#{pane_current_path}" -E
# -------------------------------------------------------------------
# Init tmux Plugin Manager
# -------------------------------------------------------------------
# Initialize tmux plugin manager (keep this line at the very bottom of tmux.conf)
#run '~/.tmux/plugins/tpm/tpm'
# Alternative TPM path when tmux config is stored under ~/.config/tmux dir.
run '~/.config/tmux/plugins/tpm/tpm'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment