Hmbown/CodeWhale · error · anyhow::Error
Shell tracking hit an internal error — restart Codewhale to
Error message
Shell tracking hit an internal error — restart Codewhale to recover.
What it means
The shell-session manager's Mutex is poisoned: some thread panicked while holding the lock, so every later .lock() returns Err and shell tracking is dead in this process. Rust poisons a mutex whenever a panic unwinds through a critical section, and request_active_foreground_shell_background maps that poisoning to this message telling the user to restart, because poisoned state is not recoverable through this path.
Source
Thrown at crates/tui/src/tui/ui.rs:3079
match request_active_foreground_shell_background(app) {
Ok(()) => {
app.status_message = Some("Moving current shell command to /jobs...".to_string());
}
Err(err) => {
app.status_message = Some(err.to_string());
}
}
}
fn request_active_foreground_shell_background(app: &App) -> Result<()> {
let shell_manager = app
.runtime_services
.shell_manager
.clone()
.context("No shell session is active.")?;
let mut manager = shell_manager.lock().map_err(|_| {
anyhow::anyhow!("Shell tracking hit an internal error — restart Codewhale to recover.")
})?;
manager.request_foreground_background();
Ok(())
}
pub(crate) fn prefill_jobs_cancel_all_if_tasks_sidebar(app: &mut App) -> bool {
if !app.view_stack.is_empty()
|| app.work_surface.panel != crate::tui::work_surface::RailPanel::Tasks
|| app.work_surface.last_area.is_none()
|| !app
.task_panel
.iter()
.any(|task| task.id.starts_with("shell_") && task.status == "running")
{
return false;
}
app.input = "/jobs cancel-all".to_string();View on GitHub (pinned to 0c42157ee5)
Solutions
- Restart the TUI as the message instructs - the poisoned lock cannot be recovered in-process
- Update to a newer build; the root cause is the original panic, so check the changelog/issue tracker for a fix
- Capture logs and the panic backtrace (RUST_BACKTRACE=1) and report it
- Avoid the interaction that triggered the original panic until the fix is installed
Defensive patterns
Strategy: try-catch
Try / catch
match shell_manager.lock() {
Ok(mut manager) => { manager.request_foreground_background(); Ok(()) }
Err(poisoned) => {
tracing::error!("shell manager lock poisoned: {:?}", poisoned);
// only if invariants are known re-establishable:
// let mut manager = poisoned.into_inner();
disable_shell_tracking_and_prompt_restart()
}
} Prevention
- Never panic while holding a shared mutex - validate inputs before locking
- Catch and log panics at thread boundaries so one bug does not poison shared state
- Consider lock poisoning recovery (into_inner) only when state invariants are re-verifiable
When it happens
Trigger: Calling request_active_foreground_shell_background() (backgrounding the foreground shell) after any earlier panic anywhere the shell_manager lock was held - e.g. a PTY I/O thread or command-tracking code path panicked and unwound through the locked region.
Common situations: A bug-triggering panic during shell output handling in an older build; stress scenarios killing worker threads; unwinding across lock boundaries.
Related errors
- xAI OAuth lifecycle lock was poisoned
- config changed after it was loaded; reload {} and retry inst
- permissions changed after they were listed; reload {} and re
- xAI OAuth lifecycle lock changed repeatedly while opening
- MCP catalog changed after tool resolution; retry the call
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/b17a1609fa1f4336.
Report an issue: GitHub.