{"record":{"id":"7dbbb71b184d77ee","repo":"can1357/oh-my-pi","slug":"native-task-tag-panicked-message","errorCode":null,"errorMessage":"native task `{tag}` panicked: {message}","messagePattern":"native task `(.+?)` panicked: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/pi-natives/src/task.rs","lineNumber":197,"sourceCode":"\t\tlet tag = self.tag;\n\t\t// Guard the napi-rs async-work FFI boundary. `execute` is registered as\n\t\t// a plain `unsafe extern \"C\" fn` (napi 3.9.4 `src/async_work.rs:109`),\n\t\t// so an unwind escaping this frame would cross a non-`C-unwind` FFI\n\t\t// edge and force-abort the host under Rust's stabilized C-unwind rules\n\t\t// (RFC 2945, stable since 1.81). The crash handler scope tells the\n\t\t// global panic hook this panic is about to be caught and mapped to a\n\t\t// `GenericFailure`, so it downgrades the report to a disk-only crash\n\t\t// log — no stderr dump, no default-hook chaining.\n\t\tmatch catch_unwind(AssertUnwindSafe(move || {\n\t\t\tcrate::crash_handler::blocking_task_panic_scope(move || work(cancel_token))\n\t\t})) {\n\t\t\tOk(result) => result,\n\t\t\tErr(payload) => {\n\t\t\t\t// Extract the message BEFORE touching the payload's destructor:\n\t\t\t\t// disposal is the one remaining step that can panic again.\n\t\t\t\tlet message = crate::crash_handler::panic_payload(&*payload);\n\t\t\t\tdispose_panic_payload(payload);\n\t\t\t\tErr(Error::new(\n\t\t\t\t\tStatus::GenericFailure,\n\t\t\t\t\tformat!(\"native task `{tag}` panicked: {message}\"),\n\t\t\t\t))\n\t\t\t},\n\t\t}\n\t}\n\n\tfn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {\n\t\tOk(output)\n\t}\n}\n\n/// Dispose of a caught panic payload without any possibility of a second\n/// unwind escaping this frame.\n///\n/// A [`std::panic::panic_any`] payload is an arbitrary user type whose `Drop`\n/// impl may itself panic. [`Blocking::compute`] runs inside napi's async-work\n/// `extern \"C\"` frame, so a panic escaping the payload's destructor would","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-natives/src/task.rs#L179-L215","documentation":"The Blocking napi Task runs work on libuv's thread pool; because that thread crosses an extern \"C\" FFI boundary, a Rust panic must not escape (it would force-abort the host). The task wraps work in catch_unwind and, if the closure panicked, converts the panic into a napi GenericFailure Error whose message names the task tag and the panic message. The JS promise rejects with this error instead of the process crashing.","triggerScenarios":"Any blocking native operation dispatched through the Blocking task whose closure panics — e.g. slice index out of bounds, unwrap on None/Err, assert failure, or an explicit panic inside the native implementation while processing your input.","commonSituations":"Feeding malformed/unexpected input to a native function (malformed buffer, invalid UTF-8 boundary); a native bug triggered by an edge case; builds with different overflow-check settings than CI.","solutions":["Read the embedded panic message after 'panicked: ' to identify the failing native operation and cause","Check the input you passed (sizes, encodings, null/undefined fields) against the function's documented contract","Retry once to rule out transient state, then report the input as a native-module bug with the panic message","Update the native module — panics here are usually fixed upstream as bugs"],"exampleFix":"// before\nconst result = await native.someBlockingTask(buf);\n// after\nlet result;\ntry {\n  result = await native.someBlockingTask(buf);\n} catch (err) {\n  if (String(err?.message).startsWith('native task `someBlockingTask` panicked')) {\n    result = null; // fall back to JS implementation\n  } else throw err;\n}","handlingStrategy":"try-catch","validationCode":"if (buf != null && typeof buf !== 'string' && !(buf instanceof Uint8Array)) throw new TypeError('expected string or Uint8Array');","typeGuard":"const isNativeInput = (v) => typeof v === 'string' || v instanceof Uint8Array;","tryCatchPattern":"try {\n  return await native.someBlockingTask(input);\n} catch (err) {\n  const msg = String(err?.message ?? err);\n  if (msg.startsWith('native task `someBlockingTask` panicked')) {\n    log.error('native panic', { panic: msg.split('panicked: ')[1] });\n    return jsFallback(input);\n  } throw err;\n}","preventionTips":["Validate input shapes/encodings before crossing into native code","Avoid boundary inputs (empty, huge, invalid UTF-8) without pre-checks","Keep the native module updated; treat panics as upstream bugs to report"],"tags":["native","panic","async-work","ffi"],"backgroundTag":"native-task-panicked","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}