BoundaryML/baml · error

dlclose failed: %s

Error message

dlclose failed: %s

What it means

This error is returned by closeLibrary when the cgo dlclose() call fails to unload the BAML native shared library on Unix platforms. dlclose() returns nonzero when the handle is invalid or the library cannot be unloaded, and the library then surfaces the OS-provided dlerror() string for diagnosis. It almost always indicates a corrupted or invalid library handle rather than a problem with BAML configuration itself.

Source

Thrown at engine/language_client_go/baml_go/lib_unix.go:80

		if errStr != "" {
			errMsg += fmt.Sprintf(": %s", errStr)
		} else {
			errMsg += ": symbol not found"
		}
		return nil, fmt.Errorf("%s", errMsg)
	}
	return symbol, nil
}

// closeLibrary closes the loaded library
func closeLibrary(handle unsafe.Pointer) error {
	if handle == nil {
		return nil
	}
	if C.dlclose(handle) != 0 {
		errStr := C.GoString(C.dlerror())
		if errStr != "" {
			return fmt.Errorf("dlclose failed: %s", errStr)
		}
		return fmt.Errorf("dlclose failed")
	}
	return nil
}

// platformInit performs any platform-specific initialization
func platformInit() error {
	// Unix doesn't need special initialization
	return nil
}

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Ensure closeLibrary is called at most once per successful loadLibrary handle; guard cleanup with a sync.Once or nil-out the handle after closing.
  2. Read the %s detail from dlerror() to identify the underlying linker error and act on it (e.g. missing dependency of the .so).
  3. Verify the bundled BAML native library file is intact and matches your baml Go module version (reinstall/refresh the dependency).
  4. If the error appears only in tests, avoid unloading the library between test cases; load once per process.

Example fix

// before
if err := closeLibrary(handle); err != nil {
    return err
}
handle = maybeStaleHandle

// after
var closeOnce sync.Once
closeOnce.Do(func() {
    if err := closeLibrary(handle); err != nil {
        log.Printf("dlclose warning: %v", err)
    }
    handle = nil
})
Defensive patterns

Strategy: try-catch

Validate before calling

if handle != nil && !closed {
    _ = closeLibrary(handle)
    closed = true
}

Type guard

func canClose(h unsafe.Pointer, closed bool) bool {
    return h != nil && !closed
}

Try / catch

if err := closeLibrary(handle); err != nil {
    log.Printf("dlclose warning (non-fatal at process exit): %v", err)
}

Prevention

When it happens

Trigger: Calling code that obtained a handle via loadLibrary (dlopen) and then calls closeLibrary with a handle that was already closed, was invalidated, or was corrupted. Also occurs if the dynamic linker refuses to unload the .so due to lingering references or linker state.

Common situations: Double-closing the library in cleanup/teardown paths; tests that load and unload the native library multiple times in one process; corrupted native binaries or mismatched library versions shipped with the Go bindings; platforms with unusual dlopen/dlclose semantics (e.g. musl vs glibc).

Related errors


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