BoundaryML/baml · error

Attempting to finish a call {:#?} without first starting one

Error message

Attempting to finish a call {:#?} without first starting one. Current context {:#?}

What it means

In `finish_call` (tracing/mod.rs:499), `ctx.exit()` returned None, meaning no call was ever started in the current runtime context. BAML requires a matching start_call/finish_call pair per context; finishing without starting is an internal invariant violation, and the message dumps the call and context for debugging.

Source

Thrown at engine/baml-runtime/src/tracing/mod.rs:499

            function_type,
            is_stream,
        );
        BAML_TRACER.lock().unwrap().put(Arc::new(trace_event));

        call
    }

    #[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();

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Ensure every `finish_call` is paired with a successful `start_call` on the same RuntimeContextManager.
  2. Check for early-return/error paths that skip start_call but still reach finish_call; guard them.
  3. Verify the context manager instance is shared (not re-created) between the start and finish of the call.
  4. Ensure finish_call is not invoked twice for the same call — the first exit() consumes the stack entry.

Example fix

// before
manager.finish_call(call, &ctx, Some(response));
// after: start first, finish exactly once
manager.start_call(&ctx, ...);
manager.finish_call(call, &ctx, Some(response));
Defensive patterns

Strategy: validation

Validate before calling

// ensure a call is active before finishing
if !ctx_has_active_call(&ctx) {
    tracing::warn!("no active BAML call to finish; skipping");
    return;
}

Try / catch

if let Err(e) = runtime.finish_call(call, &ctx, response) {
    if e.to_string().contains("without first starting one") {
        log::debug!("unpaired finish_call ignored: {e}");
    } else {
        return Err(e);
    }
}

Prevention

When it happens

Trigger: Calling `finish_call` on a `RuntimeContextManager` whose stack has no active (call_id, event_chain, tags) entry — e.g. the start call never ran, ran in a different context, or the context was already exited.

Common situations: Custom integrations (language bindings, async runtimes) that call finish without start after an early error path; context clones/moves across threads so the started call is not visible; double-finishing the same call.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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