BoundaryML/baml · error

Trace publisher channel is closed

Error message

Trace publisher channel is closed

What it means

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.

Source

Thrown at engine/baml-runtime/src/tracingv2/publisher/publisher.rs:1136

        )),
    }
}

pub fn publish_trace_event(event: Arc<TraceEventWithMeta>) -> anyhow::Result<()> {
    let Some(channel) = ensure_publisher_started() else {
        return Ok(());
    };
    match channel.try_send(PublisherMessage::Trace(event)) {
        Ok(()) => Ok(()),
        Err(mpsc::error::TrySendError::Full(_)) => {
            log::warn!(
                "Trace event queue is full (max 4 batches). Dropping trace event. \
                Consider increasing BAML_TRACE_BATCH_SIZE or reducing trace volume."
            );
            Ok(())
        }
        Err(mpsc::error::TrySendError::Closed(_)) => {
            Err(anyhow::anyhow!("Trace publisher channel is closed"))
        }
    }
}

// Note, the library we are using doesnt seem to work well for flushing in Node
// but that's ok since noone uses our wasm build in node for logging.
// https://github.com/whizsid/wasmtimer-rs/issues/26
pub async fn flush() -> anyhow::Result<()> {
    log::debug!("Flushing traces [rust]");
    // Set a timeout to avoid waiting indefinitely.
    let timeout_duration = Duration::from_secs(30);

    // First try to flush the trace publisher (which should also flush blobs internally)
    let mut publisher_result: Option<anyhow::Result<()>> = None;
    if let Some(channel) = ensure_publisher_started() {
        let (ack_tx, ack_rx) = tokio::sync::oneshot::channel();
        let send_res = channel
            .send(PublisherMessage::Flush(ack_tx))

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Stop publishing events after flush/shutdown; guard call sites with an is-active check.
  2. Re-initialize/restart the publisher before sending new events.
  3. Check logs for why the publisher worker exited (panic or fatal API error).
  4. Ignore or log the error during teardown instead of propagating it as fatal.

Example fix

// before
publisher.publish_trace_event(event)?; // panics/errors after shutdown
// after
if publisher.is_active() {
    if let Err(e) = publisher.publish_trace_event(event) {
        log::warn!("trace publish skipped: {e}");
    }
}
Defensive patterns

Strategy: type-guard

Validate before calling

// check publisher state before sending
if !publisher.is_running() {
    log::warn!("publisher shut down; dropping trace event");
    return;
}

Type guard

fn publisher_accepts_events(tx: &mpsc::Sender<Msg>) -> bool { !tx.is_closed() }

Try / catch

match publisher.publish_trace_event(ev) {
    Err(e) if e.to_string().contains("channel is closed") => {
        log::debug!("publisher already shut down; dropping event");
    }
    Err(e) => log::error!("publish failed: {e}"),
    Ok(_) => {}
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

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/41e7f1533ddb4d6b. Report an issue: GitHub.