{"record":{"id":"8eba03341d88b962","repo":"neon-bindings/neon","slug":"try-catch-unexpected-err-throw-when-vm-is-not-in-a-throwing","errorCode":null,"errorMessage":"try_catch: unexpected Err(Throw) when VM is not in a throwing state","messagePattern":"try_catch: unexpected Err\\(Throw\\) when VM is not in a throwing state","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/neon/src/context/internal.rs","lineNumber":44,"sourceCode":"impl Env {\n    pub(crate) fn to_raw(self) -> raw::Env {\n        let Self(ptr) = self;\n        ptr\n    }\n\n    pub(super) unsafe fn try_catch<T, F>(self, f: F) -> Result<T, raw::Local>\n    where\n        F: FnOnce() -> Result<T, crate::result::Throw>,\n    {\n        let result = f();\n        let mut local: MaybeUninit<raw::Local> = MaybeUninit::zeroed();\n\n        if sys::error::catch_error(self.to_raw(), local.as_mut_ptr()) {\n            Err(local.assume_init())\n        } else if let Ok(result) = result {\n            Ok(result)\n        } else {\n            panic!(\"try_catch: unexpected Err(Throw) when VM is not in a throwing state\");\n        }\n    }\n}\n\npub trait ContextInternal<'cx>: Sized {\n    fn cx(&self) -> &Cx<'cx>;\n    fn cx_mut(&mut self) -> &mut Cx<'cx>;\n    fn env(&self) -> Env {\n        self.cx().env\n    }\n}\n\nfn default_main(mut cx: ModuleContext) -> NeonResult<()> {\n    #[cfg(all(feature = \"napi-6\", feature = \"tokio-rt-multi-thread\"))]\n    crate::executor::tokio::init(&mut cx)?;\n    crate::registered().export(&mut cx)\n}\n","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/neon-bindings/neon/blob/38960e4381d9ad13b551cdf2d261f609167c9bc2/crates/neon/src/context/internal.rs#L26-L62","documentation":"`Context::try_catch` runs a closure and distinguishes a JS exception (Err(Throw)) from a Rust error. This panic fires when the closure returned Err(Throw) but the Node-API runtime reports no pending exception (`catch_error` found nothing). Neon treats that combination as an unreachable internal state, so it panics rather than silently swallowing the result.","triggerScenarios":"A closure passed to `try_catch` returns `Err(Handle<JsValue>::Throw)` while no exception is actually pending on the isolate — typically caused by custom `sys`-level code that clears or never sets a pending exception, or by manual construction of a `Throw` result outside normal error propagation.","commonSituations":"Writing low-level/native extensions that mix raw Node-API calls with Neon's try_catch; custom `Finalize` or exception-clearing code; running under an alternative runtime (e.g. Deno/older Node) whose error-catching semantics differ; Neon/sys version mismatches.","solutions":["Return ordinary `NeonResult` errors (e.g. `cx.throw_error(\"...\")?`) from the closure instead of manually building `Err(Throw)` values.","Remove any raw Node-API calls (e.g. `napi_get_and_clear_last_exception`) inside the closure that could clear a pending exception before try_catch inspects it.","Pin matching `neon` and `neon-runtime`/`sys` crate versions in Cargo.toml and rebuild.","If this reproduces on plain Neon code, file a Neon issue with a minimal reproducer — it is a library invariant violation."],"exampleFix":"// before\nfn f(cx: &mut FunctionContext) -> JsResult<JsUndefined> {\n    cx.try_catch(|cx| {\n        // manually returned Throw without a real pending exception\n        Err(cx.throw_error(\"oops\").unwrap_err())\n    })\n}\n\n// after\nfn f(cx: &mut FunctionContext) -> JsResult<JsUndefined> {\n    cx.try_catch(|cx| {\n        cx.throw_error(\"oops\")?; // real pending exception is set\n        Ok(cx.undefined())\n    })\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"cx.try_catch(|cx| {\n    // only propagate errors via cx.throw_* / NeonResult\n    let v = risky(cx)?;\n    Ok(v)\n}).or_else(|err| {\n    // handle the JS exception here; never fabricate Err(Throw) manually\n    fallback(cx)\n})","preventionTips":["Propagate errors through NeonResult and cx.throw_error, never hand-built Throw values","Don't call raw Node-API exception-clearing functions inside try_catch closures","Keep neon and its sys/runtime crates on matching versions","Reproduce weird try_catch panics in a minimal Node script before debugging app code"],"tags":["rust","neon","panics","internals"],"backgroundTag":"internal-invariant-violation","analyzedSha":"38960e4381d9ad13b551cdf2d261f609167c9bc2","analyzedAt":"2026-09-13T09:05:33.640Z","contentChangedAt":"2026-09-13T09:05:33.640Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}