astrid-runtime/astrid · error

model discovery thread panicked

Error message

model discovery thread panicked

What it means

fetch_options_blocking runs fetch_options on a dedicated thread with its own tokio runtime; if that thread panics, the join() returns Err and this error is raised instead. It converts an internal panic into a user-facing anyhow error so the caller can fall back to free-text input.

Solutions

  1. Re-run the command; if reproducible, capture the panic backtrace (RUST_BACKTRACE=1) and report the underlying bug
  2. Upgrade astrid-cli to get fixes in the discovery path
  3. Bypass interactive discovery and enter the model name manually via the fallback
  4. Check environment restrictions that could make the runtime builder or blocking thread fail
Defensive patterns

Strategy: try-catch

Try / catch

let options = match fetch_options_blocking(opts, values) {
    Ok(o) => o,
    Err(e) if e.to_string().contains("thread panicked") => {
        eprintln!("model discovery failed; enter the model name manually.");
        vec![prompt_free_text()?
    }
    Err(e) => return Err(e),
};

Prevention

When it happens

Trigger: A panic inside fetch_options or its dependencies (e.g. reqwest/tokio runtime bug, unwraps in parsing) while executing on the discovery thread.

Common situations: Upstream library panics on malformed responses; platform/runtime incompatibilities (e.g. sandboxed environments blocking thread spawn features); genuine bugs in model discovery code.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/da5a59d4a9927b75. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-cli/src/commands/capsule/model_discovery.rs:334

/// before returning, keeping the prompt strictly sequential.
///
/// Returns `Err` on any failure (mirroring [`fetch_options`]); the caller
/// maps that to a free-text fallback.
pub(crate) fn fetch_options_blocking(
    opts: &OptionsFrom,
    values: &HashMap<String, String>,
) -> anyhow::Result<Vec<String>> {
    std::thread::scope(|scope| {
        scope
            .spawn(|| {
                let runtime = tokio::runtime::Builder::new_current_thread()
                    .enable_all()
                    .build()
                    .context("failed to build discovery runtime")?;
                runtime.block_on(fetch_options(opts, values))
            })
            .join()
            .map_err(|_| anyhow::anyhow!("model discovery thread panicked"))?
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    fn vals(pairs: &[(&str, &str)]) -> HashMap<String, String> {
        pairs
            .iter()
            .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
            .collect()
    }

    #[test]
    fn resolve_template_substitutes_known_keys() {
        let v = vals(&[("base_url", "https://api.openai.com"), ("api_key", "sk-x")]);
        assert_eq!(

View on GitHub (pinned to affd8760f4)