diff --git a/src/bun.js/bindings/napi.cpp b/src/bun.js/bindings/napi.cpp index 5166bf1d55..7581b6730c 100644 --- a/src/bun.js/bindings/napi.cpp +++ b/src/bun.js/bindings/napi.cpp @@ -2970,4 +2970,10 @@ extern "C" void NapiEnv__deref(napi_env env) env->deref(); } +extern "C" bool NapiEnv__canRunFinalizer(napi_env env) +{ + // Check if the global object is still valid and VM is not terminating + return env && env->globalObject() && !env->isVMTerminating(); +} + } diff --git a/src/napi/napi.zig b/src/napi/napi.zig index d778cadd9b..69121e5e35 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -1366,6 +1366,7 @@ pub export fn napi_internal_suppress_crash_on_abort_if_desired() void { } extern fn napi_internal_remove_finalizer(env: napi_env, fun: napi_finalize, hint: ?*anyopaque, data: ?*anyopaque) callconv(.C) void; +extern fn NapiEnv__canRunFinalizer(*NapiEnv) bool; pub const Finalizer = struct { env: NapiEnv.Ref, @@ -1375,6 +1376,14 @@ pub const Finalizer = struct { pub fn run(this: *Finalizer) void { const env = this.env.get(); + + // Safety check: Ensure the env is still valid before running the finalizer. + // This prevents crashes when finalizers are enqueued but the NapiEnv is being + // torn down (e.g., when a subprocess using NAPI modules is terminating). + if (!NapiEnv__canRunFinalizer(env)) { + return; + } + const handle_scope = NapiHandleScope.open(env, false); defer if (handle_scope) |scope| scope.close(env);