can1357/oh-my-pi · error · Error
native task `{tag}` panicked: {message}
Error message
native task `{tag}` panicked: {message} What it means
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.
Source
Thrown at crates/pi-natives/src/task.rs:197
let tag = self.tag;
// Guard the napi-rs async-work FFI boundary. `execute` is registered as
// a plain `unsafe extern "C" fn` (napi 3.9.4 `src/async_work.rs:109`),
// so an unwind escaping this frame would cross a non-`C-unwind` FFI
// edge and force-abort the host under Rust's stabilized C-unwind rules
// (RFC 2945, stable since 1.81). The crash handler scope tells the
// global panic hook this panic is about to be caught and mapped to a
// `GenericFailure`, so it downgrades the report to a disk-only crash
// log — no stderr dump, no default-hook chaining.
match catch_unwind(AssertUnwindSafe(move || {
crate::crash_handler::blocking_task_panic_scope(move || work(cancel_token))
})) {
Ok(result) => result,
Err(payload) => {
// Extract the message BEFORE touching the payload's destructor:
// disposal is the one remaining step that can panic again.
let message = crate::crash_handler::panic_payload(&*payload);
dispose_panic_payload(payload);
Err(Error::new(
Status::GenericFailure,
format!("native task `{tag}` panicked: {message}"),
))
},
}
}
fn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {
Ok(output)
}
}
/// Dispose of a caught panic payload without any possibility of a second
/// unwind escaping this frame.
///
/// A [`std::panic::panic_any`] payload is an arbitrary user type whose `Drop`
/// impl may itself panic. [`Blocking::compute`] runs inside napi's async-work
/// `extern "C"` frame, so a panic escaping the payload's destructor wouldView on GitHub (pinned to 9690622007)
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
Example fix
// before
const result = await native.someBlockingTask(buf);
// after
let result;
try {
result = await native.someBlockingTask(buf);
} catch (err) {
if (String(err?.message).startsWith('native task `someBlockingTask` panicked')) {
result = null; // fall back to JS implementation
} else throw err;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (buf != null && typeof buf !== 'string' && !(buf instanceof Uint8Array)) throw new TypeError('expected string or Uint8Array'); Type guard
const isNativeInput = (v) => typeof v === 'string' || v instanceof Uint8Array;
Try / catch
try {
return await native.someBlockingTask(input);
} catch (err) {
const msg = String(err?.message ?? err);
if (msg.startsWith('native task `someBlockingTask` panicked')) {
log.error('native panic', { panic: msg.split('panicked: ')[1] });
return jsFallback(input);
} throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- bundled Silver.ttf must parse
- utoken: zstd decode failed
- modified change requires peer root
- native spelling thread stopped
- spelling range start is too large
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/7dbbb71b184d77ee.
Report an issue: GitHub.