Skip to content

napi::Error::cause is not populated when a ThreadsafeFunction callback throws #3161

@hadronzoo

Description

@hadronzoo

When a JavaScript Error with a .cause property is thrown inside a callee-handled ThreadsafeFunction callback, the resulting napi::Error on the Rust side always has cause: None, discarding the error chain.

Root cause

In call_js_cb in threadsafe_function.rs, when a pending JavaScript exception is detected in the WithCallback branch, Error is constructed directly with cause: None hardcoded:

get_error_message_and_stack_trace(raw_env, exception).and_then(|reason| {
    Err(Error {
        maybe_raw: error_reference,
        maybe_env: raw_env,
        cause: None,  // never extracted
        status: Status::from(raw_status),
        reason,
    })
})

The From<Unknown> impl in error.rs already knows how to extract .cause from a JavaScript error object:

let maybe_cause: Option<Box<Error>> = value
    .coerce_to_object()
    .and_then(|obj| obj.get_named_property::<Unknown>("cause"))
    .map(|cause| Box::new(cause.into()))
    .ok();

The exception path in call_js_cb bypasses this entirely.

Reproduction

JavaScript — throw an error with a cause:

const cause = new Error("root cause");
const err = new Error("wrapper");
err.cause = cause;
throw err;

Rust — cause is always None:

Err(err) => {
    println!("{:?}", err.cause); // None — cause is discarded
}

Expected

napi::Error::cause is populated from the thrown JavaScript error's .cause property, consistent with the behavior of From<Unknown>.

Actual

napi::Error::cause is always None, even when the thrown JavaScript exception has a populated .cause property.

Suggested fix

In the WithCallback branch of call_js_cb, extract .cause from the raw exception value using the same approach as From<Unknown> — read get_named_property::<Unknown>("cause") from the exception object and populate the cause field rather than hardcoding None.

Version

napi 3.8.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions