aaif-goose/goose · critical
goose-cli main thread panicked
Error message
goose-cli main thread panicked
What it means
After spawning the main worker thread, main() joins it; handle.join() returns Err only when that thread panicked (crates/goose-cli/src/main.rs:51-53). The panic payload is discarded and replaced by this generic message, so the real cause is whatever panicked inside the thread — most commonly the `.expect("Failed to build Tokio runtime")` or any panic inside run().
Source
Thrown at crates/goose-cli/src/main.rs:52
fn main() -> Result<()> {
#[cfg(windows)]
enable_windows_vt_processing();
let handle = std::thread::Builder::new()
.name("goose-cli-main".to_string())
.stack_size(8 * 1024 * 1024)
.spawn(|| {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed to build Tokio runtime");
runtime.block_on(run())
})
.map_err(|e| anyhow::anyhow!("Failed to spawn goose-cli main thread: {}", e))?;
handle
.join()
.map_err(|_| anyhow::anyhow!("goose-cli main thread panicked"))?
}
View on GitHub (pinned to 3810898a74)
Solutions
- Read stderr just above this error: the standard panic message ('thread ... panicked at ...') names the real failing location and payload
- Reproduce with RUST_BACKTRACE=1 to get a full backtrace of the panic
- Fix the underlying panic (often a runtime-build or configuration failure) rather than this join() wrapper
Example fix
// before (payload discarded)
handle
.join()
.map_err(|_| anyhow::anyhow!("goose-cli main thread panicked"))?
// after (surface the panic payload)
handle
.join()
.map_err(|payload| anyhow::anyhow!("goose-cli main thread panicked: {:?}", payload))? Defensive patterns
Strategy: try-catch
Try / catch
// Rust: join() returns Result — surface the panic payload instead of discarding it
match handle.join() {
Ok(result) => result,
Err(payload) => {
let reason = payload
.downcast_ref::<&str>()
.map(|s| s.to_string())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "unknown panic".into());
anyhow::bail!("goose-cli main thread panicked: {reason}")
}
} Prevention
- Run with RUST_BACKTRACE=1 during development to capture panic origins
- Fix the underlying panic identified in stderr; the join error is only a wrapper
- Prefer explicit error returns over unwrap/expect in startup paths
When it happens
Trigger: Any panic in the spawned thread: Tokio runtime construction failing (expected panic), or a panic/unwrap failure anywhere down the CLI's run() path (agent setup, provider config, etc.). The default panic hook prints the panic message to stderr before join() returns.
Common situations: A bug or unreachable state in the CLI path panicking at startup; a broken environment (e.g., invalid provider/model config causing an unwrap) that makes run() panic; the actual reason is visible in stderr just above this error line.
Related errors
- Failed to spawn goose-cli main thread: {}
- Failed to acquire registry lock
- Provider '{}' has dynamic_models: false but no static models
- missing required key {}: {}
- Anthropic provider does not support non-streaming mode. All
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/495c5e209a390205.
Report an issue: GitHub.