gitbutlerapp/gitbutler · critical

panic while executing `{}`: {} panic backtrace unavailable

Error message

panic while executing `{}`: {}

panic backtrace unavailable from hook; conversion backtrace:
{}

What it means

The degraded variant of GitButler's panic capture: the panic payload was converted into an error, but no hook snapshot existed, so thread name, panic location, and the hook backtrace are missing; a fresh Backtrace::force_capture() taken at the conversion site is attached instead. It means the same thing (an exported function panicked) with worse diagnostics — typically install_panic_hook() had not run yet, the TLS snapshot was already consumed (nested panic), or the panic happened outside the hooked path.

Source

Thrown at crates/but-api/src/panic_capture.rs:76

            .payload_message
            .as_deref()
            .or(payload_message.as_deref())
            .unwrap_or("panic payload was not a string");
        let panic_location = snapshot.location.as_deref().unwrap_or("<unknown location>");
        anyhow::anyhow!(
            "panic while executing `{}` on thread '{}' ({}) at {}: {}\n\npanic backtrace:\n{}",
            function_name,
            snapshot.thread_name,
            snapshot.thread_id,
            panic_location,
            panic_message,
            snapshot.backtrace
        )
    } else {
        let panic_message = payload_message
            .as_deref()
            .unwrap_or("panic payload was not a string");
        anyhow::anyhow!(
            "panic while executing `{}`: {}\n\npanic backtrace unavailable from hook; conversion backtrace:\n{}",
            function_name,
            panic_message,
            std::backtrace::Backtrace::force_capture()
        )
    }
}

/// Installs a hook that captures panic context and then delegates to the previous hook.
/// Can be called multiple times, but only the first call will install the hook.
pub fn install_panic_hook() {
    INSTALL_PANIC_HOOK.call_once(|| {
        let previous_hook = panic::take_hook();
        panic::set_hook(Box::new(move |panic_info| {
            capture_panic_info(panic_info);
            previous_hook(panic_info);
        }));
    });

View on GitHub (pinned to 2497b8007a)

Solutions

  1. Ensure install_panic_hook() is called before any but-api invocation — it is call_once-safe.
  2. Use the attached conversion backtrace plus the payload message to locate the panic; enable RUST_BACKTRACE=1 for more frames.
  3. Fix the underlying panic exactly as with the snapshot variant.
  4. If nested panics are involved, find the first panic — the second usually hides it.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  return await gitbutlerApi.workspaceOp();
} catch (e) {
  const msg = String(e?.message ?? e);
  if (msg.includes('panic while executing')) {
    log.error('rust panic (degraded capture — hook not installed?)', msg);
    throw new InternalError('internal error, see logs');
  }
  throw e;
}

Prevention

When it happens

Trigger: Panics in but-api functions during early startup or in tests before install_panic_hook() runs; double panics where LAST_PANIC_INFO was already taken; panics on threads where the snapshot failed to record.

Common situations: Test harnesses exercising but-api without the hook; initialization-order bugs where an API call precedes hook installation; panics raised while handling another panic.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17). Data as JSON: /api/errors/695bd0cf0a9c8b5b. Report an issue: GitHub.