gitbutlerapp/gitbutler · error · napi::Error
{e:#}
Error message
{e:#} What it means
Generated by the `but_api` macro for async API functions: any `anyhow::Error` escaping the function body is converted into a napi generic-failure Error. The message prefers a custom error-chain context message, falling back to the anyhow alternate formatting `{e:#}` (message plus `: cause` chain). It is the generic transport of all internal Rust errors to JavaScript.
Solutions
- Read the full `{e:#}` chain in the message to find the root cause.
- Validate inputs on the JS side (ids, refs, paths) before calling the API.
- Match on the message/context in JS and handle known cases (not-found, invalid-id) explicitly.
- If the message lacks context, add `.context(...)` at the failing Rust call site for better surfacing.
Defensive patterns
Strategy: try-catch
Try / catch
try {
return await api.someAsyncOperation(arg);
} catch (e) {
const chain = String(e?.message ?? e);
if (chain.includes('not found')) return null; // handle known domain cases
if (chain.includes('invalid')) throw new InputError(chain);
throw e; // unknown internal failure
} Prevention
- Validate ids/refs/paths on the JS side before every native call.
- Log the full message (it contains the anyhow cause chain) before swallowing errors.
- Ask for Rust-side .context() additions when a message is too vague to branch on.
When it happens
Trigger: Any async `but_api`-annotated function returning Err — e.g. git operation failures, file IO errors, invalid project ids — surfacing through the N-API boundary.
Common situations: Calling the desktop/lite SDK from JS with a bad commit id, missing repo state, or after an external git command mutated the worktree; underlying library errors bubble up verbatim including their cause chain.
Related errors
- spawn_blocking join error
- argument ' ' must be a non-negative integer that fits in…
- argument ' ' must be an integer that fits in isize
- {e}
- Enter an Anthropic API key
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/4f76357ac6f04433.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-api-macros/src/lib.rs:303
};
// Build the napi function body.
// For async functions, we use an `async {}` block; for sync, spawn_blocking.
// Both return anyhow::Result to handle any error type uniformly.
let napi_body = if asyncness.is_some() {
quote! {
let __napi_body_result: ::anyhow::Result<::serde_json::Value> = async {
let result = #napi_call_fn_args?;
#convert_to_json_result_type
Ok(::serde_json::to_value(result)?)
}.await;
__napi_body_result.map_err(|e: ::anyhow::Error| {
let ctx = but_error::AnyhowContextExt::custom_context_or_error_chain(&e);
let message = ctx
.message
.map(|m| m.to_string())
.unwrap_or_else(|| format!("{e:#}"));
napi::Error::new(napi::Status::GenericFailure, message)
})
}
} else {
// For sync functions, param conversions must be inside spawn_blocking
// so that non-Send types (e.g. Context with Rc) are never moved across threads.
quote! {
::tokio::task::spawn_blocking(move || {
#(#napi_param_conversions);*
let __napi_body_result: ::anyhow::Result<::serde_json::Value> = (|| {
let result = #napi_call_fn_args?;
#convert_to_json_result_type
Ok(::serde_json::to_value(result)?)
})();
__napi_body_result.map_err(|e: ::anyhow::Error| {
let ctx = but_error::AnyhowContextExt::custom_context_or_error_chain(&e);
let message = ctx
.message
.map(|m| m.to_string())View on GitHub (pinned to 58e5313667)