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

  1. Use a dedicated RuntimeContextManager (or proper context stack) per concurrent call so ids stay paired.
  2. Do not reuse a TracingCall after finishing it; create a fresh call per invocation.
  3. Check that the same TracingCall passed to start_call is passed to finish_call.
  4. 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

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


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