astrid-runtime/astrid · error

headless configuration error state was poisoned

Error message

headless configuration error state was poisoned

What it means

During non-interactive (`seal`) installs, configuration errors are accumulated in a shared `Mutex`-guarded `headless_errors` list and reported at the end by `run_with_elicit`. This error is thrown when the mutex is poisoned — i.e. another thread panicked while holding the lock — so the collected error list cannot be read reliably.

Solutions

  1. Inspect earlier logs for the panic that poisoned the mutex — that root panic is the real failure
  2. Retry the install; if reproducible, file a bug with the capsule manifest and command line
  3. Update the CLI/capsule — panics in config collection are bugs to fix at the source
Defensive patterns

Strategy: try-catch

Try / catch

match result {
    Err(e) if e.to_string().contains("poisoned") => {
        // Root cause is an earlier panic; re-run with backtrace and inspect logs.
        eprintln!("Headless install crashed earlier (poisoned lock); re-run with RUST_BACKTRACE=1.");
    }
    other => other?,
}

Prevention

When it happens

Trigger: A worker thread/task that shares `headless_errors` panics while holding the lock (e.g. a bug in a config callback), then the finishing code in `run_with_elicit` calls `.lock()` on the poisoned mutex.

Common situations: A panic inside elicit/config collection tasks during headless installs (often from `.unwrap()` in a prompt formatter or malformed manifest triggering an unexpected panic); only surfaces after a prior panic, so check earlier log output.

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/df1d80e1daf440fb. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-cli/src/commands/capsule/install_finish.rs:48

            let vars = prompt.vars.clone();
            let errors = std::sync::Arc::clone(&headless_errors);
            handle.spawn(async move {
                headless_elicit_handler(receiver, bus_for_handler, vars, errors).await;
            })
        } else {
            handle.spawn(async move {
                cli_elicit_handler(receiver, bus_for_handler).await;
            })
        }
    });
    let result = f(opts, event_bus.clone());
    if let Some(task) = elicit_task {
        task.abort();
    }
    drop(event_bus);
    let errors = headless_errors
        .lock()
        .map_err(|_| anyhow::anyhow!("headless configuration error state was poisoned"))?;
    if !errors.is_empty() {
        bail!(
            "non-interactive capsule configuration failed: {}",
            errors.join("; ")
        );
    }
    result
}

/// Validate install output, surface diagnostics, and persist manual install
/// configuration before returning the installed capsule identity.
pub(super) fn finish_install(
    output: &InstallOutput,
    home: &AstridHome,
    principal: &astrid_core::PrincipalId,
    prompt: &ManualInstallOptions,
) -> anyhow::Result<InstalledCapsuleOutcome> {
    let batch = BATCH_MODE.load(std::sync::atomic::Ordering::Relaxed);

View on GitHub (pinned to affd8760f4)