BoundaryML/baml · error · napi::Error
Already used span
Error message
Already used span
What it means
Thrown by the N-API Rust binding for a BAML runtime span when finish() is called on a Span that has already been finished or consumed. The span's inner runtime call handle is taken with Option::take(), so a second finish() finds None and raises this error. It guards against double-completing a span, which would corrupt runtime tracing.
Solutions
- Ensure finish() is called at most once per span (use a boolean flag or wrap in a once-guard)
- Check whether an earlier code path (catch/finally) already finished the span before calling finish again
- If you need multiple completions, create a new span instead of reusing the finished one
- Catch this napi Error and treat it as an idempotent no-op if double-finish is expected in your flow
Example fix
// before
span.finish(result, ctx, env);
span.finish(result, ctx, env); // throws 'Already used span'
// after
let finished = false;
function finishOnce() {
if (!finished) {
finished = true;
span.finish(result, ctx, env);
}
} Defensive patterns
Strategy: try-catch
Validate before calling
if (span.__finished) { return; } Type guard
function isFinishable(span) { return span && typeof span.finish === 'function' && !span.__finished; } Try / catch
try { span.finish(result, ctx, envVars); } catch (e) {
if (String(e?.message).includes('Already used span')) { /* idempotent no-op */ }
else throw e;
} Prevention
- Wrap finish in a once-only helper
- Never finish the same span in both success and error paths
- Track span lifecycle explicitly in instrumentation code
When it happens
Trigger: Calling span.finish(result, ctx, envVars) twice on the same span object; reusing a span returned by an earlier finish; wrapping finish() in retry logic that re-invokes it after a prior successful call.
Common situations: Instrumentation code that finishes spans in multiple code paths (success and error handlers) without tracking completion; promise/callback flows where a span is finished both in a then and a catch; wrapping frameworks that call finish on teardown after the app already finished the span.
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
- attempted to read a property that has no defined type, this…
- BamlError: BamlClientError: Something went wrong with the…
- BamlError: BamlInvalidArgumentError
- BamlError: BamlInvalidArgumentError
- BamlError: Unexpected error from BAML
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/20b6fa5ddce8601b.
Report an issue: GitHub.
Appendix: source
Thrown at engine/language_client_typescript/src/types/span.rs:60
})
}
// mthod to finish
#[napi]
pub fn finish(
&mut self,
result: serde_json::Value,
ctx: &RuntimeContextManager,
env_vars: serde_json::Value,
) -> napi::Result<serde_json::Value> {
log::trace!("Finishing span: {:?}", self.inner);
let result: BamlValue = serde_json::from_value(result)
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, format!("{e:?}")))?;
let call = self
.inner
.take()
.ok_or_else(|| napi::Error::new(napi::Status::GenericFailure, "Already used span"))?;
let env_vars: HashMap<String, String> = serde_json::from_value(env_vars)
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, format!("{e:?}")))?;
self.rt
.finish_call(call, Some(result), &ctx.inner, &env_vars)
.map(|u| u.to_string())
.map(|u| serde_json::json!(u))
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, format!("{e:?}")))
}
}
View on GitHub (pinned to bd85ce9dee)