Hmbown/CodeWhale · critical · anyhow::Error
Codewhale runtime thread panicked: {message}
Error message
Codewhale runtime thread panicked: {message} What it means
crates/tui/src/lib.rs runs the entire async runtime on a dedicated 'codewhale-main' thread with a fixed stack size (CODEWHALE_MAIN_STACK_BYTES). If code inside run_async_main panics, thread::join() returns the panic payload; this site downcasts it to &str or String and rewraps it as 'Codewhale runtime thread panicked: {message}'. The message names the panic value, not the file:line where it happened.
Source
Thrown at crates/tui/src/lib.rs:1681
// events all share one async owner. Debug builds retain enough stack
// temporaries that nesting a modal event over the TUI loop can exceed the
// platform main-thread default (8 MiB on macOS). Give that owner an
// explicit stack while keeping process hardening and the global panic hook
// above this boundary, before Tokio or any worker thread exists.
let runtime_thread = std::thread::Builder::new()
.name("codewhale-main".to_string())
.stack_size(CODEWHALE_MAIN_STACK_BYTES)
.spawn(move || run_async_main(cli, command, plugin_discovery, plugin_registry))
.context("Failed to start the Codewhale runtime thread")?;
match runtime_thread.join() {
Ok(result) => result,
Err(payload) => {
let message = payload
.downcast_ref::<&str>()
.map(|value| (*value).to_string())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "unknown panic payload".to_string());
Err(anyhow!("Codewhale runtime thread panicked: {message}"))
}
}
}
fn run_async_main(
cli: Cli,
command: Option<Commands>,
plugin_discovery: Arc<crate::plugins::PluginDiscoveryContext>,
plugin_registry: Arc<crate::plugins::PluginRegistry>,
) -> Result<()> {
build_runtime()?.block_on(run_async_main_inner(
cli,
command,
plugin_discovery,
plugin_registry,
))
}
View on GitHub (pinned to 8880682c63)
Solutions
- Re-run with RUST_BACKTRACE=1 (or full) — the panic hook prints the real file:line to stderr before this join error is returned
- Reproduce with the same subcommand and inputs, then fix the unwrap/expect at the reported location
- If depth/recursion related, raise CODEWHALE_MAIN_STACK_BYTES or move the deep work off the fixed-stack thread
- Report the panic location and backtrace if it comes from released code
Defensive patterns
Strategy: try-catch
Try / catch
match runtime_thread.join() {
Ok(result) => result,
Err(payload) => {
let message = payload
.downcast_ref::<&str>()
.map(|value| (*value).to_string())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "unknown panic payload".to_string());
Err(anyhow!("Codewhale runtime thread panicked: {message}"))
}
} Prevention
- Run with RUST_BACKTRACE=1 so the panic hook prints the real file:line before join returns
- Replace unwrap/expect on fallible startup data with error propagation
- Keep the default panic hook installed — it carries the location this error omits
- Wrap third-party code that may panic in std::panic::catch_unwind where feasible
When it happens
Trigger: Any panic inside run_async_main: an unwrap/expect on None or Err, an out-of-bounds index, an assertion, a panic inside a dependency, or 'unknown panic payload' when the payload is not a string type.
Common situations: A startup regression panics on an unexpected config shape or empty model registry; a dependency panics on malformed input; deep recursion exceeds the configured stack size and the runtime aborts into the panic path.
Related errors
- ${name} is unavailable in Workflow scripts: runs must be det
- new Date()/Date() is unavailable in Workflow scripts: runs m
- task(): expected an options object
- ${envelope.error}
- parallel(): expected an array of thunks
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/4cb6d858874ac809.
Report an issue: GitHub.