{"record":{"id":"41e7f1533ddb4d6b","repo":"BoundaryML/baml","slug":"trace-publisher-channel-is-closed","errorCode":null,"errorMessage":"Trace publisher channel is closed","messagePattern":"Trace publisher channel is closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/baml-runtime/src/tracingv2/publisher/publisher.rs","lineNumber":1136,"sourceCode":"        )),\n    }\n}\n\npub fn publish_trace_event(event: Arc<TraceEventWithMeta>) -> anyhow::Result<()> {\n    let Some(channel) = ensure_publisher_started() else {\n        return Ok(());\n    };\n    match channel.try_send(PublisherMessage::Trace(event)) {\n        Ok(()) => Ok(()),\n        Err(mpsc::error::TrySendError::Full(_)) => {\n            log::warn!(\n                \"Trace event queue is full (max 4 batches). Dropping trace event. \\\n                Consider increasing BAML_TRACE_BATCH_SIZE or reducing trace volume.\"\n            );\n            Ok(())\n        }\n        Err(mpsc::error::TrySendError::Closed(_)) => {\n            Err(anyhow::anyhow!(\"Trace publisher channel is closed\"))\n        }\n    }\n}\n\n// Note, the library we are using doesnt seem to work well for flushing in Node\n// but that's ok since noone uses our wasm build in node for logging.\n// https://github.com/whizsid/wasmtimer-rs/issues/26\npub async fn flush() -> anyhow::Result<()> {\n    log::debug!(\"Flushing traces [rust]\");\n    // Set a timeout to avoid waiting indefinitely.\n    let timeout_duration = Duration::from_secs(30);\n\n    // First try to flush the trace publisher (which should also flush blobs internally)\n    let mut publisher_result: Option<anyhow::Result<()>> = None;\n    if let Some(channel) = ensure_publisher_started() {\n        let (ack_tx, ack_rx) = tokio::sync::oneshot::channel();\n        let send_res = channel\n            .send(PublisherMessage::Flush(ack_tx))","sourceCodeStart":1118,"sourceCodeEnd":1154,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/engine/baml-runtime/src/tracingv2/publisher/publisher.rs#L1118-L1154","documentation":"Raised in publish_trace_event when the mpsc channel to the trace publisher worker is closed (mpsc::error::TrySendError::Closed). It means the publisher background task has already terminated (or was never started), so trace events can no longer be accepted. Any subsequent event submissions will fail until the publisher is restarted.","triggerScenarios":"Calling publish_trace_event after the publisher task was shut down (flush/shutdown completed, runtime dropped) or after the worker task panicked/exited due to a previous fatal error.","commonSituations":"Application publishing trace events after calling flush during shutdown, calling publish from a different thread after the runtime was dropped, or a prior unrecoverable error (e.g. auth failure) tearing down the worker.","solutions":["Stop publishing events after flush/shutdown; guard call sites with an is-active check.","Re-initialize/restart the publisher before sending new events.","Check logs for why the publisher worker exited (panic or fatal API error).","Ignore or log the error during teardown instead of propagating it as fatal."],"exampleFix":"// before\npublisher.publish_trace_event(event)?; // panics/errors after shutdown\n// after\nif publisher.is_active() {\n    if let Err(e) = publisher.publish_trace_event(event) {\n        log::warn!(\"trace publish skipped: {e}\");\n    }\n}","handlingStrategy":"type-guard","validationCode":"// check publisher state before sending\nif !publisher.is_running() {\n    log::warn!(\"publisher shut down; dropping trace event\");\n    return;\n}","typeGuard":"fn publisher_accepts_events(tx: &mpsc::Sender<Msg>) -> bool { !tx.is_closed() }","tryCatchPattern":"match publisher.publish_trace_event(ev) {\n    Err(e) if e.to_string().contains(\"channel is closed\") => {\n        log::debug!(\"publisher already shut down; dropping event\");\n    }\n    Err(e) => log::error!(\"publish failed: {e}\"),\n    Ok(_) => {}\n}","preventionTips":["Enforce a lifecycle rule: no publishes after flush/shutdown","Check channel-closed state before submitting events from other threads","Log (don't propagate) publish failures during teardown","Watch for worker panics that close the channel early"],"tags":["async","channel","baml","tracing","lifecycle"],"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-14T11:17:12.474Z"}