firecrawl/pdf-inspector · error · napi::Error (Status::GenericFailure)

{ctx}: Rust panic: {msg}

Error message

{ctx}: Rust panic: {msg}

What it means

catch_panic in napi/src/lib.rs wraps Rust closures so that a panic (unwind) inside the native module is converted into a NAPI Error with message "{ctx}: Rust panic: {msg}" instead of aborting the Node process. Rust panics normally abort or unwind across FFI, which is UB/abort in native modules; this converts them into a catchable JS Error. The payload is the panic message (&str, String, or 'unknown panic').

Source

Thrown at napi/src/lib.rs:436

}

/// Run a closure, catching any Rust panic and converting it to a NAPI error.
/// Prevents process abort from unwind panics in the native module.
fn catch_panic<F, T>(ctx: &str, f: F) -> Result<T>
where
    F: FnOnce() -> Result<T> + panic::UnwindSafe,
{
    match panic::catch_unwind(f) {
        Ok(result) => result,
        Err(payload) => {
            let msg = if let Some(s) = payload.downcast_ref::<&str>() {
                s.to_string()
            } else if let Some(s) = payload.downcast_ref::<String>() {
                s.clone()
            } else {
                "unknown panic".to_string()
            };
            Err(Error::new(
                Status::GenericFailure,
                format!("{ctx}: Rust panic: {msg}"),
            ))
        }
    }
}

// ---------------------------------------------------------------------------
// Shared implementations (single body behind sync and async entry points)
// ---------------------------------------------------------------------------

fn process_pdf_impl(bytes: &[u8], pages: Option<Vec<u32>>) -> Result<PdfResult> {
    let mut opts = pdf_inspector::PdfOptions::new();
    if let Some(p) = pages {
        opts = opts.pages(p);
    }
    let result = pdf_inspector::process_pdf_mem_with_options(bytes, opts)
        .map_err(|e| to_napi_err(e, "process_pdf"))?;

View on GitHub (pinned to 636ca1a58b)

Solutions

  1. Capture the message after 'Rust panic:' and reduce the input to a minimal PDF that reproduces it, then file a bug with that file.
  2. Wrap the binding call in try/catch in Node.js so one bad document doesn't take down the process — this error IS the catchable form of the panic.
  3. Isolate risky documents in a worker thread/child process so even an abort cannot kill the main process.
  4. Upgrade the package — panics on valid PDFs are bugs that get fixed; check the changelog for the panic site mentioned in the message.

Example fix

// before
const md = pdf2md.processFileSync(pdfPath);
// after
let md;
try {
  md = pdf2md.processFileSync(pdfPath);
} catch (e) {
  if (String(e.message).includes('Rust panic:')) {
    console.error('native panic on', pdfPath, e.message);
    md = null; // skip / quarantine this document
  } else { throw e; }
}
Defensive patterns

Strategy: try-catch

Validate before calling

function looksLikePdf(buf) {
  const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
  return b.length > 5 && b.subarray(0, 5).toString('latin1').startsWith('%PDF-');
}

Type guard

function isPanicError(e) {
  return e instanceof Error && e.message.includes(': Rust panic: ');
}

Try / catch

try {
  return pdf2md.processFileSync(pdfPath);
} catch (e) {
  if (isPanicError(e)) {
    quarantine(pdfPath, e.message.split(': Rust panic: ')[1]);
    return null; // isolate: don't let one doc break the batch
  }
  throw e;
}

Prevention

When it happens

Trigger: Any pdf2md NAPI binding call whose Rust internals hit a panic: unreachable!()/unwrap()/expect failure, index-out-of-bounds, integer overflow in debug, or assertion inside extraction/table/layout code.

Common situations: Processing an unusual or malformed PDF that triggers an unhandled edge case (e.g. corrupt content stream, extreme coordinates); running a release binary compiled with panic=abort bypassing this guard; hitting a library bug on a specific document.

Related errors


AI-assisted analysis of firecrawl/pdf-inspector@636ca1a58b (2026-09-05). Data as JSON: /api/errors/281491e82ec545b8. Report an issue: GitHub.