neon-bindings/neon · error

Failed to unwrap napi_external as Box

Error message

Failed to unwrap napi_external as Box<Any>

What it means

`JsBox::from_local` converts a raw N-API local handle back into a `JsBox<T>`. It expects the handle's associated external data to be a `Box<dyn Any>` holding the boxed Rust value; the `.expect` fires when `maybe_external_deref` returns None — the local is not an napi_external carrying Neon's boxed data. This is an internal invariant violation: the handle was not created by `JsBox::new`, so the cast is invalid.

Solutions

  1. Ensure the local handle originates from `JsBox::new`/Neon's boxing path
  2. Use checked conversion APIs (e.g. `cx.argument::<JsBox<T>>(i)`) that return an error instead of unwrapping
  3. Audit manual N-API/FFI code that attaches externals so it uses Neon's expected Box<Any> layout

Example fix

Only call `JsBox::from_local` (or APIs that go through it) on handles produced by `JsBox::new`; type-check with `cx.argument::<JsBox<T>>()` which validates instead of panicking.
Defensive patterns

Strategy: type-guard

Prevention

When it happens

Trigger: `from_local` panics when unwrapping an `Option<Box<dyn Any>>` from `maybe_external_deref(env, local)` yields None — i.e. the raw local handle does not wrap a Neon-managed napi_external (wrong value type passed where a JsBox is expected).

Common situations: Passing a plain JS object, function, or a foreign external to an API that reconstructs a `JsBox<T>`; FFI/manual N-API code creating externals outside Neon; type confusion between native handles.


AI-assisted analysis of neon-bindings/neon@38960e4381 (2026-09-13). Data as JSON: /api/errors/690e6c3f43247610. Report an issue: GitHub.

Appendix: source

Thrown at crates/neon/src/types_impl/boxed.rs:214

    fn downcast<Other: Value>(cx: &mut Cx, other: &Other) -> Option<Self> {
        let local = other.to_local();
        let data = unsafe { maybe_external_deref(cx.env(), local) };

        // Attempt to downcast the `Option<&BoxAny>` to `Option<*const T>`
        data.and_then(|v| v.downcast_ref())
            .map(|raw_data| Self(JsBoxInner { local, raw_data }))
    }

    fn to_local(&self) -> raw::Local {
        self.0.local
    }

    unsafe fn from_local(env: Env, local: raw::Local) -> Self {
        let raw_data = unsafe { maybe_external_deref(env, local) }
            .expect("Failed to unwrap napi_external as Box<Any>")
            .downcast_ref()
            .expect("Failed to downcast Any");

        Self(JsBoxInner { local, raw_data })
    }

View on GitHub (pinned to 38960e4381)