{"record":{"id":"4f76357ac6f04433","repo":"gitbutlerapp/gitbutler","slug":"e","errorCode":null,"errorMessage":"{e:#}","messagePattern":"\\{e:#\\}","errorType":"exception","errorClass":"napi::Error","httpStatus":null,"severity":"error","filePath":"crates/but-api-macros/src/lib.rs","lineNumber":303,"sourceCode":"    };\n\n    // Build the napi function body.\n    // For async functions, we use an `async {}` block; for sync, spawn_blocking.\n    // Both return anyhow::Result to handle any error type uniformly.\n    let napi_body = if asyncness.is_some() {\n        quote! {\n            let __napi_body_result: ::anyhow::Result<::serde_json::Value> = async {\n                let result = #napi_call_fn_args?;\n                #convert_to_json_result_type\n                Ok(::serde_json::to_value(result)?)\n            }.await;\n            __napi_body_result.map_err(|e: ::anyhow::Error| {\n                let ctx = but_error::AnyhowContextExt::custom_context_or_error_chain(&e);\n                let message = ctx\n                    .message\n                    .map(|m| m.to_string())\n                    .unwrap_or_else(|| format!(\"{e:#}\"));\n                napi::Error::new(napi::Status::GenericFailure, message)\n            })\n        }\n    } else {\n        // For sync functions, param conversions must be inside spawn_blocking\n        // so that non-Send types (e.g. Context with Rc) are never moved across threads.\n        quote! {\n            ::tokio::task::spawn_blocking(move || {\n                #(#napi_param_conversions);*\n                let __napi_body_result: ::anyhow::Result<::serde_json::Value> = (|| {\n                    let result = #napi_call_fn_args?;\n                    #convert_to_json_result_type\n                    Ok(::serde_json::to_value(result)?)\n                })();\n                __napi_body_result.map_err(|e: ::anyhow::Error| {\n                    let ctx = but_error::AnyhowContextExt::custom_context_or_error_chain(&e);\n                    let message = ctx\n                        .message\n                        .map(|m| m.to_string())","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-api-macros/src/lib.rs#L285-L321","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await api.someAsyncOperation(arg);\n} catch (e) {\n  const chain = String(e?.message ?? e);\n  if (chain.includes('not found')) return null;      // handle known domain cases\n  if (chain.includes('invalid')) throw new InputError(chain);\n  throw e; // unknown internal failure\n}","preventionTips":["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."],"tags":["napi","nodejs","anyhow","error-propagation"],"backgroundTag":"api-error-response","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}