sxyazi/yazi · error · anyhow::Error

Tab with id {id} not found

Error message

Tab with id {id} not found

What it means

Ctx::new resolves the target tab for every action: if the Action carries a "tab" id it is looked up with core.mgr.tabs.idx(id), otherwise the current cursor tab is used. An id that matches no open tab makes construction fail with "Tab with id {id} not found" before the actor runs. Tab ids are only valid while that tab is alive; closing a tab invalidates previously captured ids.

Source

Thrown at yazi-actor/src/context.rs:32

	pub source:    Source,
	#[cfg(debug_assertions)]
	pub backtrace: Vec<&'static str>,
}

impl Deref for Ctx<'_> {
	type Target = Core;

	fn deref(&self) -> &Self::Target { self.core }
}

impl DerefMut for Ctx<'_> {
	fn deref_mut(&mut self) -> &mut Self::Target { self.core }
}

impl<'a> Ctx<'a> {
	pub fn new(action: &Action, core: &'a mut Core, term: &'a mut Option<Raterm>) -> Result<Self> {
		let tab = if let Ok(id) = action.get::<Id>("tab") {
			core.mgr.tabs.idx(id).ok_or_else(|| anyhow!("Tab with id {id} not found"))?
		} else {
			core.mgr.tabs.cursor
		};

		Ok(Self {
			core,
			term,
			tab,
			level: 0,
			source: action.source,
			#[cfg(debug_assertions)]
			backtrace: vec![],
		})
	}

	pub fn with<F, T>(&mut self, tab: usize, f: F) -> T
	where
		F: FnOnce(&mut Self) -> T,

View on GitHub (pinned to 94abcfa92f)

Solutions

  1. Drop the "tab" argument from the action so Ctx::new falls back to the current tab (core.mgr.tabs.cursor)
  2. Re-read the live tab id from the latest Tab event payload instead of caching it across events
  3. Check validity first: core.mgr.tabs.idx(id) (and tabs.len()) before constructing the action
  4. If the tab was legitimately closed, treat the error as benign and ignore/skip the action

Example fix

// before: stale cached id
emit!(Call(relay!(mgr:hover).with("tab", cached_id)));

// after: let Ctx pick the current tab
emit!(Call(relay!(mgr:hover)));
Defensive patterns

Strategy: validation

Validate before calling

// Before emitting an action with an explicit tab id:
if core.mgr.tabs.idx(id).is_none() {
    // stale id: fall back to the current tab
    emit!(Call(relay!(mgr:hover)));
    return Ok(());
}
emit!(Call(relay!(mgr:hover).with("tab", id)));

Type guard

fn tab_exists(mgr: &Mgr, id: Id) -> bool { mgr.tabs.idx(id).is_some() }

Try / catch

match Ctx::new(&action, core, term) {
    Ok(cx) => { /* run actor */ }
    Err(e) if e.to_string().starts_with("Tab with id") => { /* stale tab: drop or re-emit without `tab` */ }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Emitting or relaying an action that includes a "tab" argument (e.g. relay!(mgr:...).with("tab", id)) after that tab was closed, or passing an id that was never allocated by mgr.tabs. Also any deferred/queued action whose captured id goes stale before dispatch.

Common situations: Plugins caching a tab id from an earlier Tab event and emitting actions later; keymap or plugin snippets hardcoding a tab id; driving a yazi instance via ya-emitted actions while the user closes tabs in between.

Related errors


AI-assisted analysis of sxyazi/yazi@94abcfa92f (2026-08-16). Data as JSON: /api/errors/9ee5bc740a6933be. Report an issue: GitHub.