wezterm/wezterm · error

invalid pane id {}

Error message

invalid pane id {}

What it means

In TmuxDomainState::split_pane(), after the tab is found, the source pane must be located among the tab's panes via iter_panes_ignoring_zoom() to compute the split geometry. This bail fires when pane_id is not one of that tab's panes: the pane was closed locally in the interim, or the pane belongs to a different tab than the tab_id supplied. The pane id and tab id have become inconsistent.

Source

Thrown at mux/src/tmux_commands.rs:252

        &self,
        tab_id: TabId,
        pane_id: PaneId,
        remote_id: TmuxPaneId,
        split_request: SplitRequest,
    ) -> anyhow::Result<Arc<dyn Pane>> {
        let mux = Mux::get();
        let tab = match mux.get_tab(tab_id) {
            Some(t) => t,
            None => anyhow::bail!("Invalid tab id {}", tab_id),
        };

        let pane_index = match tab
            .iter_panes_ignoring_zoom()
            .iter()
            .find(|p| p.pane.pane_id() == pane_id)
        {
            Some(p) => p.index,
            None => anyhow::bail!("invalid pane id {}", pane_id),
        };

        let split_size = match tab.compute_split_size(pane_index, split_request) {
            Some(s) => s,
            None => anyhow::bail!("invalid pane index {}", pane_index),
        };

        let window_id = match self.gui_tabs.lock().iter().find(|t| t.1.tab_id == tab_id) {
            Some((_, tab)) => tab.tmux_window_id,
            None => anyhow::bail!("No tab {}", tab_id),
        };

        let p = PaneItem {
            session_id: 0,
            window_id: window_id,
            pane_id: remote_id,
            _pane_index: 0,
            cursor_x: 0,

View on GitHub (pinned to 9c04f79f86)

Solutions

  1. Validate the pair before resolving: tab.iter_panes_ignoring_zoom().iter().any(|p| p.pane.pane_id() == pane_id)
  2. If stale, drop the split (return no pane) instead of erroring — the next ListAllPanes prune reconciles state
  3. Capture (tab_id, pane_id) atomically from a single mux snapshot when queueing splits

Example fix

// before
let pane_index = match tab.iter_panes_ignoring_zoom().iter().find(|p| p.pane.pane_id() == pane_id) {
    Some(p) => p.index,
    None => anyhow::bail!("invalid pane id {}", pane_id),
};

// after
let Some(pane_index) = tab.iter_panes_ignoring_zoom().iter().find(|p| p.pane.pane_id() == pane_id).map(|p| p.index) else {
    log::debug!("pane {pane_id} no longer in tab {tab_id}; dropping split");
    return Ok(None);
};
Defensive patterns

Strategy: validation

Validate before calling

// Validate the (tab, pane) pair before split resolution:
let valid = mux
    .get_tab(tab_id)
    .map(|t| t.iter_panes_ignoring_zoom().iter().any(|p| p.pane.pane_id() == pane_id))
    .unwrap_or(false);
if !valid {
    return Ok(None); // pane left the tab; drop the split
}

Type guard

fn pane_in_tab(tab: &Arc<Tab>, pane_id: PaneId) -> bool {
    tab.iter_panes_ignoring_zoom().iter().any(|p| p.pane.pane_id() == pane_id)
}

Try / catch

match state.split_pane(tab_id, pane_id, remote_id, req) {
    Ok(pane) => Some(pane),
    Err(e) if e.to_string().starts_with("invalid pane id") => None, // stale; next sync reconciles
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Resolving a pending split after the source pane was closed (pane exit, window closed) while tab_id stayed valid; calling split_pane with a (tab_id, pane_id) pair captured at different times; a pane pruned by remove_detached_pane between split request and resolution.

Common situations: Fast pane churn in tmux (processes exiting with remain-on-exit off); splits racing window resyncs that rebuild tab membership.

Related errors


AI-assisted analysis of wezterm/wezterm@9c04f79f86 (2026-08-16). Data as JSON: /api/errors/3b8b37fd823bf7c6. Report an issue: GitHub.