BoundaryML/baml · error · anyhow::Error

No root state found for local variable

Error message

No root state found for local variable: {:?}

What it means

When building a watch Notification for a local-variable node, from_node_id looks up the variable's root state in vm.watch. The map lookup failing means the local variable (by stack index) has no registered root state — i.e. the variable was never put under watch or the state was cleared — yet a notification was requested for it.

Solutions

  1. Verify the variable was registered via watch before requesting notifications for its NodeId
  2. Check that the stack_index captured when subscribing still matches the current frame layout
  3. Guard the call site so notifications are only fetched for variables with an active root state
  4. Clear pending LocalVar notification requests when watches are unsubscribed or frames are popped

Example fix

// before
vm.watch.root_state(*node_id)
    .map(|state| Notification::Channel(state.channel.clone()))
    .ok_or_else(|| anyhow::anyhow!("No root state found for local variable: {:?}", stack_index))
// after
match vm.watch.root_state(*node_id) {
    Some(state) => Ok(Notification::Channel(state.channel.clone())),
    None => Ok(Notification::Ignored), // variable is no longer watched
}
Defensive patterns

Strategy: fallback

Validate before calling

fn is_watched(vm: &Vm, node: &watch::NodeId) -> bool {
    vm.watch.root_state_for(node).is_some()
}

Try / catch

match Notification::from_node_id(vm, node_id) { Ok(n) => n, Err(_) => Notification::Ignored }

Prevention

When it happens

Trigger: Calling from_node_id with NodeId::LocalVar(stack_index) for a variable that has no root state registered in vm.watch (never subscribed, or subscription dropped/cleared before the notification was generated).

Common situations: Debugger/watch tooling requesting notifications for stack slots after a frame was popped or before a watch subscription completed; off-by-one stack indices after register allocation changes.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/d7338326b0d7eed6. Report an issue: GitHub.

Appendix: source

Thrown at engine/baml-vm/src/test.rs:219

    pub fn viz(function_name: String, event: VizExecEvent) -> Self {
        Notification::Viz {
            function_name,
            event,
        }
    }
}

impl Notification {
    /// Convert from VM NodeId to test Node by resolving indices to names/objects.
    pub fn from_node_id(node_id: &watch::NodeId, vm: &Vm) -> anyhow::Result<Self> {
        match node_id {
            watch::NodeId::LocalVar(stack_index) => vm
                .watch
                .root_state(*node_id)
                .map(|state| Notification::Channel(state.channel.clone()))
                .ok_or_else(|| {
                    anyhow::anyhow!("No root state found for local variable: {:?}", stack_index)
                }),
            watch::NodeId::HeapObject(_obj_index) => {
                Ok(Notification::Object(Object::String("bogger".to_string())))
            }
        }
    }
}

/// Enhanced test execution state that supports test Value comparisons.
#[derive(Debug, Clone, PartialEq)]
pub enum ExecState {
    /// VM cannot proceed. It is awaiting a pending future to complete.
    Await(Object),
    /// VM notifies caller about a future that needs to be scheduled.
    ScheduleFuture(Object),
    /// VM has completed the execution with a test-friendly value.
    Complete(Value),

View on GitHub (pinned to bd85ce9dee)