{"record":{"id":"20b6fa5ddce8601b","repo":"BoundaryML/baml","slug":"already-used-span","errorCode":null,"errorMessage":"Already used span","messagePattern":"Already used span","errorType":"exception","errorClass":"napi::Error","httpStatus":null,"severity":"error","filePath":"engine/language_client_typescript/src/types/span.rs","lineNumber":60,"sourceCode":"        })\n    }\n\n    // mthod to finish\n    #[napi]\n    pub fn finish(\n        &mut self,\n        result: serde_json::Value,\n        ctx: &RuntimeContextManager,\n        env_vars: serde_json::Value,\n    ) -> napi::Result<serde_json::Value> {\n        log::trace!(\"Finishing span: {:?}\", self.inner);\n        let result: BamlValue = serde_json::from_value(result)\n            .map_err(|e| napi::Error::new(napi::Status::GenericFailure, format!(\"{e:?}\")))?;\n\n        let call = self\n            .inner\n            .take()\n            .ok_or_else(|| napi::Error::new(napi::Status::GenericFailure, \"Already used span\"))?;\n\n        let env_vars: HashMap<String, String> = serde_json::from_value(env_vars)\n            .map_err(|e| napi::Error::new(napi::Status::GenericFailure, format!(\"{e:?}\")))?;\n\n        self.rt\n            .finish_call(call, Some(result), &ctx.inner, &env_vars)\n            .map(|u| u.to_string())\n            .map(|u| serde_json::json!(u))\n            .map_err(|e| napi::Error::new(napi::Status::GenericFailure, format!(\"{e:?}\")))\n    }\n}\n","sourceCodeStart":42,"sourceCodeEnd":72,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/engine/language_client_typescript/src/types/span.rs#L42-L72","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nspan.finish(result, ctx, env);\nspan.finish(result, ctx, env); // throws 'Already used span'\n// after\nlet finished = false;\nfunction finishOnce() {\n  if (!finished) {\n    finished = true;\n    span.finish(result, ctx, env);\n  }\n}","handlingStrategy":"try-catch","validationCode":"if (span.__finished) { return; }","typeGuard":"function isFinishable(span) { return span && typeof span.finish === 'function' && !span.__finished; }","tryCatchPattern":"try { span.finish(result, ctx, envVars); } catch (e) {\n  if (String(e?.message).includes('Already used span')) { /* idempotent no-op */ }\n  else throw e;\n}","preventionTips":["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"],"tags":["rust","napi","span","tracing","double-finish"],"backgroundTag":"invalid-state-transition","analyzedSha":"bd85ce9dee1463ff04d27efd20531013a4ff46c1","analyzedAt":"2026-09-12T03:38:25.718Z","contentChangedAt":"2026-09-12T03:38:25.718Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}