Hmbown/CodeWhale · error
cannot discard a loaded Runtime thread
Error message
cannot discard a loaded Runtime thread
What it means
`discard_empty_thread` only removes threads that are unloaded and have no turns. If an engine for the thread is currently loaded in `active.engines`, discarding would yank state out from under a live engine, so the call bails immediately. A second guard rejects threads whose `latest_turn_id` is set.
Solutions
- Unload/close the engine for the thread first, then call discard_empty_thread.
- Check `active.engines.contains_key(thread_id)` before attempting discard.
- If the thread has turns, use a non-empty-thread deletion path or accept that it cannot be discarded as empty.
- Serialize cleanup with thread-loading so the check-then-discard is not racing an open.
Example fix
// before runtime.discard_empty_thread(thread_id).await?; // engine may be loaded // after runtime.unload_engine(thread_id).await?; runtime.discard_empty_thread(thread_id).await?;
Defensive patterns
Strategy: validation
Validate before calling
if runtime.is_thread_loaded(thread_id) {
anyhow::bail!("unload the engine before discarding thread {}", thread_id);
} Try / catch
match runtime.discard_empty_thread(thread_id).await {
Err(e) if e.to_string().contains("loaded Runtime thread") => {
runtime.unload_engine(thread_id).await?;
runtime.discard_empty_thread(thread_id).await?;
}
other => other,
} Prevention
- Unload engines before running thread cleanup sweeps.
- Check latest_turn_id before discarding; threads with turns need a different deletion path.
- Coordinate cleanup with the UI so it cannot race a thread being opened.
When it happens
Trigger: Calling discard_empty_thread while the thread's engine is loaded (present in active.engines); also fails if the thread owns any turns.
Common situations: Cleanup routines sweeping stale threads that race with a user re-opening the thread; UI calling discard on the thread currently displayed; automated tests not unloading engines before cleanup.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- cannot discard a Runtime thread that owns turns
- Automation admission execution ownership is unverified or…
- Cannot continue agent
- Check for an available update first.
- Codewhale runtime thread panicked
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/d23069780c0434ce.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:7392
&thread.id,
None,
None,
"thread.started",
json!({ "thread": thread.clone() }),
)
.await
{
self.active.lock().await.shell_managers.remove(&thread.id);
let _ = self.store.remove_thread(&thread.id);
return Err(error);
}
Ok(thread)
}
pub(crate) async fn discard_empty_thread(&self, thread_id: &str) -> Result<()> {
let mut active = self.active.lock().await;
if active.engines.contains_key(thread_id) {
bail!("cannot discard a loaded Runtime thread");
}
let _thread_mutation = self.store.thread_mutation.lock();
let thread = self.store.load_thread(thread_id)?;
if thread.latest_turn_id.is_some() {
bail!("cannot discard a Runtime thread that owns turns");
}
// Drop the thread's shell authority with it: the manager owns any
// API-created jobs, and dropping the last handle kills them.
active.shell_managers.remove(thread_id);
drop(active);
self.store.remove_thread(thread_id)
}
pub async fn list_threads(
&self,
filter: ThreadListFilter,
limit: Option<usize>,
) -> Result<Vec<ThreadRecord>> {View on GitHub (pinned to 73e0f67d83)