Skip to main content
Multiplexer (mux) events are emitted by WezTerm’s multiplexer layer, which manages terminal sessions, panes, tabs, and windows. These events allow you to customize multiplexer behavior and startup.

mux-startup

Since: 20220624-141144-bd1b7c5d Emitted once when the mux server starts up.

Timing

  • Fires before any default program is started
  • Fires BEFORE gui-startup event
  • If this event creates panes, they take precedence over default program configuration
  • Fires in both local and remote (daemon) mode

Event Signature

wezterm.on('mux-startup', function()
  -- No parameters
end)

Return Value

No return value expected. This is a fire-and-forget event.

Examples

Basic Window Split

local wezterm = require 'wezterm'
local mux = wezterm.mux

-- Called when the mux server starts
wezterm.on('mux-startup', function()
  local tab, pane, window = mux.spawn_window {}
  pane:split { direction = 'Top' }
end)

return {
  unix_domains = {
    { name = 'unix' },
  },
}

Multi-Pane Development Layout

wezterm.on('mux-startup', function()
  -- Create main window with editor
  local tab, pane, window = mux.spawn_window {
    cwd = wezterm.home_dir .. '/projects',
  }
  
  -- Create horizontal split for terminal
  local bottom_pane = pane:split {
    direction = 'Bottom',
    size = 0.3,
  }
  
  -- Create vertical split in bottom for logs
  bottom_pane:split {
    direction = 'Right',
    size = 0.5,
  }
end)

Workspace Initialization

wezterm.on('mux-startup', function()
  -- Create multiple workspaces
  local workspaces = {
    { name = 'main', cwd = wezterm.home_dir },
    { name = 'dev', cwd = wezterm.home_dir .. '/dev' },
    { name = 'ops', cwd = '/var/log' },
  }
  
  for _, ws in ipairs(workspaces) do
    mux.spawn_window {
      workspace = ws.name,
      cwd = ws.cwd,
    }
  end
  
  mux.set_active_workspace 'main'
end)

Use Cases

  • Setting up consistent terminal layouts
  • Creating default workspaces
  • Initializing multiplexer-only sessions (daemon mode)
  • Pre-configuring pane arrangements before GUI attachment

mux-is-process-stateful

Since: 20220101-133340-7edc5b5a Emitted when the multiplexer needs to determine if a pane can be closed without prompting.

Characteristics

  • Synchronous event - Must return quickly to avoid blocking
  • Called before closing a pane to check if confirmation is needed
  • Allows custom logic beyond the default process name matching

Event Signature

wezterm.on('mux-is-process-stateful', function(proc)
  -- proc is a LocalProcessInfo object
  -- Return true, false, or nil
end)
proc
LocalProcessInfo
Process information object containing details about the process tree in the pane.Fields:
  • pid - Process ID
  • name - Process name
  • status - Process status (e.g., “Sleep”, “Running”)
  • argv - Array of command-line arguments
  • executable - Full path to executable
  • cwd - Current working directory
  • children - Table of child processes (keyed by PID)

Return Values

true
boolean
Process is stateful - prompt user before closing
false
boolean
Process is not stateful - close without prompting
nil
nil
Use default behavior (check skip_close_confirmation_for_processes_named config)

Examples

Log Process Tree

local wezterm = require 'wezterm'

function log_proc(proc, indent)
  indent = indent or ''
  wezterm.log_info(
    indent
      .. 'pid='
      .. proc.pid
      .. ', name='
      .. proc.name
      .. ', status='
      .. proc.status
  )
  wezterm.log_info(indent .. 'argv=' .. table.concat(proc.argv, ' '))
  wezterm.log_info(
    indent .. 'executable=' .. proc.executable .. ', cwd=' .. proc.cwd
  )
  for pid, child in pairs(proc.children) do
    log_proc(child, indent .. '  ')
  end
end

wezterm.on('mux-is-process-stateful', function(proc)
  log_proc(proc)
  return nil -- Use default behavior
end)

return {}
Example Output:
INFO  config::lua > lua: pid=1913470, name=zsh, status=Sleep
INFO  config::lua > lua: argv=-zsh
INFO  config::lua > lua: executable=/usr/bin/zsh, cwd=/home/user
INFO  config::lua > lua:   pid=1913567, name=bash, status=Sleep
INFO  config::lua > lua:   argv=bash
INFO  config::lua > lua:   executable=/usr/bin/bash, cwd=/home/user
INFO  config::lua > lua:     pid=1913624, name=vim, status=Sleep
INFO  config::lua > lua:     argv=vim foo
INFO  config::lua > lua:     executable=/usr/bin/vim, cwd=/home/user

Custom Stateful Process Detection

-- Check if any process in the tree is an editor
local function is_editor_running(proc)
  local editors = { 'vim', 'nvim', 'emacs', 'nano', 'code' }
  
  for _, editor in ipairs(editors) do
    if proc.name == editor then
      return true
    end
  end
  
  -- Check children recursively
  for _, child in pairs(proc.children) do
    if is_editor_running(child) then
      return true
    end
  end
  
  return false
end

wezterm.on('mux-is-process-stateful', function(proc)
  if is_editor_running(proc) then
    return true -- Always prompt when editor is running
  end
  return nil -- Use default for other processes
end)

Directory-Based Detection

wezterm.on('mux-is-process-stateful', function(proc)
  -- Always prompt if working in important directories
  local protected_dirs = {
    '/etc',
    wezterm.home_dir .. '/important-project',
  }
  
  for _, dir in ipairs(protected_dirs) do
    if proc.cwd:find(dir, 1, true) == 1 then
      wezterm.log_info('Process in protected directory: ' .. proc.cwd)
      return true
    end
  end
  
  return nil
end)

Long-Running Process Detection

-- Track process start times (simplified example)
local process_times = {}

wezterm.on('mux-is-process-stateful', function(proc)
  local now = os.time()
  
  if not process_times[proc.pid] then
    process_times[proc.pid] = now
  end
  
  local runtime = now - process_times[proc.pid]
  
  -- Prompt if process has been running for more than 1 hour
  if runtime > 3600 then
    wezterm.log_info(
      string.format('Long-running process detected: %s (%ds)',
        proc.name, runtime)
    )
    return true
  end
  
  return nil
end)

Use Cases

  • Custom logic for determining important processes
  • Protecting specific workflows or directories
  • Integration with project-specific tools
  • Preventing accidental closure of development environments

Event Ordering

When WezTerm starts, multiplexer events fire first:
1. mux-startup          ← Multiplexer initializes
2. gui-startup          ← GUI layer starts
3. gui-attached         ← GUI attaches to domain
For wezterm connect DOMAIN:
1. mux-startup          ← Mux starts (if new daemon)
2. gui-attached         ← GUI attaches to domain
                          (gui-startup does NOT fire)

Common Patterns

Daemon Mode Setup

local wezterm = require 'wezterm'
local mux = wezterm.mux

wezterm.on('mux-startup', function()
  -- Setup for daemon/server mode
  local home = wezterm.home_dir
  
  -- Create a persistent development workspace
  local dev_tab, dev_pane, dev_window = mux.spawn_window {
    workspace = 'dev',
    cwd = home .. '/dev',
  }
  
  -- Bottom pane for build output
  local build_pane = dev_pane:split {
    direction = 'Bottom',
    size = 0.25,
  }
  
  -- Create monitoring workspace
  local mon_tab, mon_pane, mon_window = mux.spawn_window {
    workspace = 'monitoring',
    args = { 'htop' },
  }
  
  -- Default to dev workspace
  mux.set_active_workspace 'dev'
end)

return {
  unix_domains = {
    { name = 'unix' },
  },
}

Combining mux-startup with gui-startup

-- mux-startup: Create persistent structure
wezterm.on('mux-startup', function()
  -- This runs once when mux starts
  mux.spawn_window { workspace = 'persistent' }
end)

-- gui-startup: Configure GUI-specific settings
wezterm.on('gui-startup', function(cmd)
  -- This runs each time GUI starts
  local tab, pane, window = mux.spawn_window(cmd or {})
  window:gui_window():maximize()
end)

See Also