BoundaryML/baml · warning · ExposedError::AbortError

AbortError: {detailed_message}

Error message

AbortError: {detailed_message}

What it means

ExposedError::AbortError is raised by the orchestrator when a BAML call is cancelled while awaiting LLM responses. In the tokio::select! race, if the cancel_future completes first, the runtime records a Cancelled LLMResponse and an AbortError with a (here empty) detailed message, signaling the operation was aborted before completion.

Source

Thrown at engine/baml-runtime/src/internal/llm_client/orchestrator/call.rs:85

        Some(token) => Box::pin(async move {
            token.cancelled_owned().await;
        })
            as std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>,
        None => Box::pin(futures::future::pending()),
    };
    tokio::pin!(cancel_future);

    for node in iter {
        // Check for cancellation at the start of each iteration
        let cancel_scope = node.scope.clone();
        tokio::select! {
            biased;

            _ = &mut cancel_future => {
                results.push((
                    cancel_scope,
                    LLMResponse::Cancelled("Operation cancelled".to_string()),
                    Some(Err(anyhow::anyhow!(
                        crate::errors::ExposedError::AbortError {
                            detailed_message: String::new()
                        }
                    ))),
                ));
                break;
            }
            result = async {
                let prompt = match node.render_prompt(ir, prompt, ctx, params).await {
                    Ok(p) => p,
                    Err(e) => {
                        return Some((
                            node.scope,
                            LLMResponse::InternalFailure(e.to_string()),
                            Some(Err(anyhow::anyhow!(e.to_string()))),
                        ));
                    }
                };

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Handle the abort gracefully: catch AbortError and return a 499/cancelled-style response rather than a 500.
  2. Check caller-side cancellation (AbortController, request context) is intentional; fix accidental future-dropping in your code.
  3. If aborts are spurious, ensure the future/tokio task driving orchestrate() is not dropped early (hold the JoinHandle or use select branches deliberately).
  4. Add retry-with-backoff only for idempotent calls if cancellation is timeout-based.

Example fix

// before
let result = baml_fn.run(ctx, params).await?; // AbortError surfaces as 500
// after
match baml_fn.run(ctx, params).await {
    Err(e) if is_abort(&e) => Ok(HttpResponse::cancelled()),
    other => other.map_err(Into::into),
}
Defensive patterns

Strategy: try-catch

Type guard

fn is_abort_error(err: &anyhow::Error) -> bool {
    err.downcast_ref::<crate::errors::ExposedError::AbortError>().is_some()
}

Try / catch

match orchestrate_result {
    Err(e) if is_abort_error(&e) => {
        tracing::info!("llm call aborted by client");
        return Ok(Cancelled);
    }
    other => other?,
}

Prevention

When it happens

Trigger: Any orchestrate() run where the caller's cancellation future resolves first: HTTP client disconnects, dropped request handles, explicit aborts, or timeout-driven cancellation while an LLM call is in flight.

Common situations: Browser users navigate away or React Query aborts fetches; server middleware times out requests; downstream consumers drop the stream/token driving the BAML function call.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/ad490579d858402f. Report an issue: GitHub.