BoundaryML/baml · error

undefined global item: {name_str}

Error message

undefined global item: {name_str}

What it means

When emitting a `Constant::GlobalItem` (a non-function global such as a client or a top-level `let`), the compiler reads the global slot assigned by the `$init` pass from the `globals` map keyed by the item's rendered name. This panic fires when the name is absent — the global was never assigned a slot in the current compilation unit. It is effectively an "undefined global" compiler-internal error, analogous to an unresolved symbol at link time.

Source

Thrown at baml_language/crates/baml_compiler2_emit/src/emit.rs:2185

            Constant::Function(item_ref) => {
                // A plain function reference as a VALUE. Pooled exactly like
                // `Constant::GenericFunction`, with EMPTY type args: every
                // function-pointer value on the heap is a wrapper object
                // (`GenericFunction`/`Closure`/`BoundMethod`/`HostClosure`),
                // and a raw `Object::Function` is never a data value — the
                // invariant `value_concrete_ty` / `callable_signature` rely
                // on. Interning keeps `greet === greet` pointer-stable, as a
                // direct `LoadGlobal` of the function object did before.
                self.emit_pooled_function_value(item_ref, &[]);
            }
            Constant::GlobalItem(item_ref) => {
                // A non-function global item (a client, a top-level `let`,
                // ...): read the value `$init` stored in its slot, unwrapped.
                let name_str = item_ref.to_string();
                let global_idx = *self
                    .globals
                    .get(&name_str)
                    .unwrap_or_else(|| panic!("undefined global item: {name_str}"));
                self.references.record(&name_str);
                let inst = self.emit(Instruction::LoadGlobal(GlobalIndex::from_raw(global_idx)));
                self.set_operand(inst, OperandMeta::Global(name_str));
            }
            Constant::GenericFunction { item, type_args } => {
                // `foo<int>` as a value: the same pooled wrapper, carrying its
                // concrete type arguments so calling it seeds `frame.type_args`.
                self.emit_pooled_function_value(item, type_args);
            }
            Constant::EnumVariant { enum_ref, variant } => {
                let enum_name_str = enum_ref.to_string();
                // Gracefully handle undefined enum references (e.g. cross-package
                // references that aren't registered in this compilation context).
                // Emit a Null constant so tests don't panic; runtime will fail
                // if the code path is actually executed.
                let enum_obj_idx = self.enum_object_indices.get(&enum_name_str).copied();
                if enum_obj_idx.is_some() {
                    self.references.record(&enum_name_str);

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Ensure the referenced client/top-level `let` is defined in a package included in the current compilation
  2. Clear incremental caches and do a full rebuild so `$init` re-slots all globals
  3. Check for renames/typos of the global name in the source
  4. If the name comes from another package, confirm cross-package registration/exports are set up
  5. Report a compiler bug if a defined global still triggers the panic (key-mismatch regression)

Example fix

// before (BAML referencing a client that isn't in the compiled set)
client MyClient { ... } // defined in another, uncompiled package
// after
// include the package defining MyClient in the compilation, or define it locally:
client MyClient { provider "openai" options { model "gpt-4o" } }
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight: every referenced global item must have a slot
for name in referenced_global_names {
    assert!(globals.contains_key(&name), "global not in compilation unit: {name}");
}

Try / catch

match std::panic::catch_unwind(|| compile(inputs)) {
    Ok(out) => out,
    Err(_) => {
        clean_incremental_cache();
        compile(inputs) // full rebuild after stale-state panic
    }
}

Prevention

When it happens

Trigger: Referencing a top-level `let` or client from code being emitted when that item's slot is missing — e.g. the global lives in a package not included in this compilation context, the global was removed but a stale incremental fragment still references it, or the rendered name key diverges between slot assignment and lookup.

Common situations: Cross-package references to clients/top-level values not registered in this compile; stale incremental state after deleting or renaming a client or top-level `let`; compiler refactors changing `ItemRef::to_string` so the two maps use different key spellings.

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/8e32af4b5941a864. Report an issue: GitHub.