BoundaryML/baml · error · CtypesError

Invalid handle key: {0}

Error message

Invalid handle key: {0}

What it means

CtypesError::InvalidHandleKey(u64) indicates a handle-table key passed across the bridge does not resolve to any live entry in HANDLE_TABLE. The key may be stale (already released), from another process, or simply wrong.

Source

Thrown at baml_language/crates/bridge_ctypes/src/error.rs:14

//! Error types used by the shared ctypes conversion logic.

use thiserror::Error;

/// Errors that can occur during value encoding/decoding for the bridge.
#[derive(Debug, Error)]
pub enum CtypesError {
    #[error("Protobuf decode error: {0}")]
    ProtobufDecode(#[from] prost::DecodeError),

    #[error("Null buffer pointer")]
    NullBuffer,

    #[error("Invalid handle key: {0}")]
    InvalidHandleKey(u64),

    #[error("Map entry missing key")]
    MapEntryMissingKey,

    /// Carries only the input length, not the input itself — untrusted hex
    /// blobs can be up to the FFI decode cap (~67M chars), and embedding
    /// them in error messages bloats logs and exposes payload contents.
    #[error("Invalid bigint hex string ({len} bytes)")]
    InvalidBigint { len: usize },

    /// Carries only the input length for over-cap decimal type literals, so a
    /// hostile descriptor cannot amplify logs by echoing its full payload.
    #[error("Invalid decimal bigint literal ({len} bytes)")]
    InvalidBigintLiteral { len: usize },

    #[error(
        "Invalid InboundValue.value_type: a root union or optional does not identify one exact selected type"

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Keep the handle alive for as long as you use the key; call release_handle exactly once per ownership.
  2. Re-acquire the handle (e.g. media_from_url/... ) if it was released; do not cache keys across sessions.
  3. Log the key and check live_handle_count/refcount instrumentation to confirm it is live.
  4. Search for double-release paths in the host code (release on both success and error paths).

Example fix

// before
release_handle(key);  # ok
release_handle(key);  # InvalidHandleKey / invalid handle
// after
if not released.contains(key):
    release_handle(key)
    released.add(key)
Defensive patterns

Strategy: try-catch

Validate before calling

# check liveness before use (test instrumentation)
assert bridge.live_handle_count() > 0 and key in live_keys

Try / catch

try:
    media = bridge.media_url(key, handle_type)
except BridgeError as e:
    if str(e).startswith("Invalid handle key"):
        media = reacquire_media()  # re-mint the handle
    else:
        raise

Prevention

When it happens

Trigger: Calling clone_handle/release_handle/media_* accessors with a key that was already released, never created, or outlived its table row; using a key from a previous bridge session.

Common situations: Double-free of handles (releasing twice); lifetime bugs where the host dropped a handle but still uses it; serializing keys across process restarts; ID reuse assumptions after upgrades.

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