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
- Handle the abort gracefully: catch AbortError and return a 499/cancelled-style response rather than a 500.
- Check caller-side cancellation (AbortController, request context) is intentional; fix accidental future-dropping in your code.
- If aborts are spurious, ensure the future/tokio task driving orchestrate() is not dropped early (hold the JoinHandle or use select branches deliberately).
- 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
- Ensure the task/future driving the LLM call outlives the request; don't drop JoinHandles that carry the call.
- Set explicit, generous timeouts so cancellations are intentional rather than incidental.
- Map AbortError to a client-disconnect status (e.g. HTTP 499) in your API layer instead of surfacing it as a server error.
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
- Operation cancelled: {message}
- {self}\n\nDetailed message: {detailed_message}
- Operation cancelled..
- ExposedError::AbortError { detailed_message }
- BamlAbortError: Operation was aborted: {msg}
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/ad490579d858402f.
Report an issue: GitHub.