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
- Validate the pair before resolving: tab.iter_panes_ignoring_zoom().iter().any(|p| p.pane.pane_id() == pane_id)
- If stale, drop the split (return no pane) instead of erroring — the next ListAllPanes prune reconciles state
- 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
- Capture (tab_id, pane_id) atomically from one mux snapshot when queueing splits
- Re-validate pair membership at resolution time; panes close independently of tabs
- Let the ListAllPanes prune reconcile remote state instead of erroring on stale local ids
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
- Could not find the tmux pane peer for local pane: {pane_id}
- No tab {}
- Split_pane failed
- The window {window_id} is not attached
- Tmux pane already attached
AI-assisted analysis of wezterm/wezterm@9c04f79f86 (2026-08-16).
Data as JSON: /api/errors/3b8b37fd823bf7c6.
Report an issue: GitHub.