gitbutlerapp/gitbutler · error · napi::Error
InvalidArg
InvalidArg
Error message
argument '{}' must be a non-negative integer that fits in usize What it means
Generated by but-api-macros for napi-exported functions taking usize: the JS value arrives as i64 and is narrowed with TryFrom; a negative number (or a value beyond the platform usize range, relevant on 32-bit builds) yields napi Status::InvalidArg with this message naming the argument. It is a boundary type-check that runs before the Rust function executes; fractional/NaN input is rejected earlier by napi's own number conversion.
Source
Thrown at crates/but-api-macros/src/lib.rs:1560
names.push(ident.to_string());
conversions.push(quote! {
let #ident: crate::json::HexHash = ::std::str::FromStr::from_str(&#ident)
.map(crate::json::HexHash)
.map_err(|e: gix::hash::decode::Error| napi::Error::new(napi::Status::InvalidArg, format!("{e}")))?;
});
call_arg_idents.push(quote! { #ident });
}
_ => {
// For all other types: check for napi-incompatible types first
if let Some(napi_ty) = napi_type_remap(base_ty) {
// Type needs remapping (e.g., usize → i64)
params.push(quote! { #ident: #napi_ty });
names.push(ident.to_string());
let arg_name = ident.to_string();
let conversion = match type_name.as_deref() {
Some("usize") => quote! {
let #ident: usize = ::std::convert::TryFrom::try_from(#ident).map_err(|_| {
napi::Error::new(
napi::Status::InvalidArg,
format!(
"argument '{}' must be a non-negative integer that fits in usize",
#arg_name
),
)
})?;
},
Some("isize") => quote! {
let #ident: isize = ::std::convert::TryFrom::try_from(#ident).map_err(|_| {
napi::Error::new(
napi::Status::InvalidArg,
format!(
"argument '{}' must be an integer that fits in isize",
#arg_name
),
)
})?;View on GitHub (pinned to 2497b8007a)
Solutions
- Validate in JS before the call: integer, >= 0, and <= Number.MAX_SAFE_INTEGER.
- Replace -1 sentinels with an explicit optional/null parameter in the binding signature.
- Clamp or reject out-of-range inputs with a user-facing message at the caller.
Example fix
// before
await listEntries({ limit: -1 }); // "argument 'limit' must be a non-negative integer..."
// after
const limit = userLimit < 0 ? undefined : userLimit; // omit to use the Rust default
await listEntries({ limit }); Defensive patterns
Strategy: validation
Validate before calling
if (!Number.isInteger(limit) || limit < 0 || !Number.isSafeInteger(limit)) {
throw new TypeError('limit must be a non-negative safe integer');
} Type guard
const isUsize = (v: unknown): v is number => typeof v === 'number' && Number.isInteger(v) && v >= 0 && v <= Number.MAX_SAFE_INTEGER;
Prevention
- Avoid negative sentinel values in JS-to-Rust APIs; model optionals explicitly.
- Assert integer ranges at the JS boundary with a shared helper.
- Keep numeric API parameters within safe-integer range by design.
When it happens
Trigger: From Node/Electron, calling a generated but-api binding whose Rust signature takes usize with -1 (a common 'unlimited' sentinel), values above Number.MAX_SAFE_INTEGER, or values above u32 range on 32-bit targets.
Common situations: UI code passing -1 as 'no limit'; arithmetic overflow producing huge counts in computed sizes; JSON configs deserialized with negative defaults.
Related errors
- panic while executing `{}` on thread '{}' ({}) at {}: {} pa
- panic while executing `{}`: {} panic backtrace unavailable
- Validation
AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17).
Data as JSON: /api/errors/4f60e082a2ccf9a8.
Report an issue: GitHub.