BoundaryML/baml · error · io::Error

history boundary {} was not begun

Error message

history boundary {} was not begun

What it means

append_log_body requires that a history boundary was previously begun with the given BoundaryId. The boundaries map holds per-boundary state; if the ID is absent, an io::Error of kind NotFound is produced saying the boundary was not begun. It surfaces as a NotFound error from append_log_body.

Source

Thrown at baml_language/crates/bex_events/src/history/mod.rs:180

                writer,
            },
        );
        Ok(())
    }

    pub fn append_log_body(
        &self,
        boundary_id: BoundaryId,
        event: LogEventRecord,
        codec: ValueCodec,
        body: Vec<u8>,
    ) -> io::Result<ValueWriteOutcome> {
        let mut inner = self
            .inner
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner);
        let state = inner.boundaries.get_mut(&boundary_id).ok_or_else(|| {
            io::Error::new(
                io::ErrorKind::NotFound,
                format!(
                    "history boundary {} was not begun",
                    boundary_id.to_wire_string()
                ),
            )
        })?;
        state.writer.append_log_body(event, codec, body)
    }

    pub fn append_capture_loss(
        &self,
        boundary_id: BoundaryId,
        record: &CaptureLossRecord,
    ) -> io::Result<()> {
        let mut inner = self
            .inner
            .lock()

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Call begin_boundary (or the boundary-opening API) before append_log_body and reuse the returned BoundaryId.
  2. Ensure the same History instance that began the boundary is used for appends.
  3. Synchronize threads so begin completes before any append on that boundary.
  4. Check the ID's wire string in the message against registered boundaries to spot ID mix-ups.

Example fix

// before
history.append_log_body(boundary_id, record)?;
// after
let boundary_id = history.begin_boundary(&run_started)?;
history.append_log_body(boundary_id, record)?;
Defensive patterns

Strategy: validation

Validate before calling

if !begun_boundaries.contains(&boundary_id) {
    return Err("append_log_body called before begin_boundary".into());
}

Try / catch

match history.append_log_body(boundary_id, record) {
    Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
        let id = history.begin_boundary(&run_started)?;
        history.append_log_body(id, record)?
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling append_log_body(boundary_id, ...) with a BoundaryId that was never created via begin_boundary, or one that came from a different History instance/thread.

Common situations: Race between threads where logs are appended before the boundary-start call completes; reusing a stale boundary ID after a History was recreated; ordering bugs in test harnesses (e.g. replay_orders_logs_chronologically_across_threads) that append before begin.

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/4c5b94ce887890f6. Report an issue: GitHub.