BoundaryML/baml · error
Call ID mismatch: {} != {}
Error message
Call ID mismatch: {} != {} What it means
In `finish_call` (tracing/mod.rs:507), the call_id popped from the context via `ctx.exit()` does not match `call.call_id`. BAML correlates a finished call with the context entry it started under; a mismatch means the call being finished does not correspond to the active traced call, so the runtime bails to avoid attributing results to the wrong trace.
Source
Thrown at engine/baml-runtime/src/tracing/mod.rs:507
#[cfg(target_arch = "wasm32")]
pub(crate) async fn finish_call(
&self,
call: TracingCall,
ctx: &RuntimeContextManager,
response: Option<BamlValue>,
) -> Result<uuid::Uuid> {
let guard = self.trace_stats.guard();
let Some((call_id, event_chain, tags)) = ctx.exit() else {
anyhow::bail!(
"Attempting to finish a call {:#?} without first starting one. Current context {:#?}",
call,
ctx
);
};
if call.call_id != call_id {
anyhow::bail!("Call ID mismatch: {} != {}", call.call_id, call_id);
}
if let Some(tracer) = &self.tracer {
tracer
.submit(response.to_log_schema(&self.options, event_chain, tags, call))
.await?;
guard.done();
Ok(call_id)
} else {
guard.done();
Ok(call_id)
}
}
// For non-LLM function calls -- used by FFI boundary like with @trace in python
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn finish_call(
&self,View on GitHub (pinned to bd85ce9dee)
Solutions
- Use a dedicated RuntimeContextManager (or proper context stack) per concurrent call so ids stay paired.
- Do not reuse a TracingCall after finishing it; create a fresh call per invocation.
- Check that the same TracingCall passed to start_call is passed to finish_call.
- Serialize start/finish pairs if calls share a context, or finish them in LIFO order.
Example fix
// before: shared context across concurrent calls let ctx = RuntimeContextManager::new(); tokio::join!(finish(a, &ctx), finish(b, &ctx)); // after: one context per call let ctx_a = RuntimeContextManager::new(); let ctx_b = RuntimeContextManager::new(); tokio::join!(finish(a, &ctx_a), finish(b, &ctx_b));
Defensive patterns
Strategy: validation
Validate before calling
// verify pairing before finishing assert_eq!(call.call_id, active_call_id_of(&ctx), "TracingCall/context mismatch");
Try / catch
if let Err(e) = runtime.finish_call(call, &ctx, response) {
if e.to_string().contains("Call ID mismatch") {
log::error!("crossed trace calls; isolate contexts per invocation: {e}");
} else {
return Err(e);
}
} Prevention
- One RuntimeContextManager per concurrent BAML invocation.
- Pass the identical TracingCall used at start to finish.
- Never reuse TracingCall instances after finishing.
- Finish calls in LIFO order when nesting on shared contexts.
When it happens
Trigger: Two interleaved traced calls sharing one RuntimeContextManager — finishing call A pops call B's id; a `TracingCall` struct cloned/reused across contexts; finishing calls out of the order they were started.
Common situations: Concurrent BAML function invocations sharing a context manager in async code; reusing a TracingCall after its first finish; mixed nested function calls where the wrong TracingCall handle is passed to finish_call.
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
- Attempting to finish a call {:#?} without first starting one
- Attempting to finish a call without first starting one
- history boundary {} was not begun
- No BAML sources loaded. Use :load <path> to load sources.
- No tracer found
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/9bd5e7f668144f9d.
Report an issue: GitHub.