gitbutlerapp/gitbutler · error · napi::Error
InvalidArg
InvalidArg
Error message
invalid '{}' at index {index}: {e} What it means
Emitted by macro-generated argument conversion in but-api-macros when a JS caller passes a Vec of strings that must parse as gix::ObjectId (Vec<ObjectId> surfaced as Vec<HexHash>/Vec<String>). If any entry fails hex/length parsing, the whole conversion fails with napi InvalidArg naming the parameter and the failing index.
Solutions
- Check the entry at the reported index and fix or remove it; the message names both the parameter and index.
- Validate every entry client-side with /^[0-9a-f]{40}$/i (or 64 for SHA-256 repos) before calling the API.
- Ensure you are passing object IDs, not ref names or prefixed IDs like 'main' or 'origin/main'.
Example fix
// before
await api.someFn(['4c1f0ab...', 'oops']);
// after
const isOid = (s: string) => /^[0-9a-f]{40,64}$/i.test(s);
if (!ids.every(isOid)) throw new Error('invalid commit id in list');
await api.someFn(ids); Defensive patterns
Strategy: validation
Validate before calling
const OID_RE = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/i; // SHA-1 or SHA-256
function assertOidList(ids: string[]): void {
ids.forEach((id, i) => {
if (!OID_RE.test(id)) throw new TypeError(`ids[${i}] is not a valid object id: ${id}`);
});
} Type guard
function isOidList(ids: unknown): ids is string[] {
return (
Array.isArray(ids) &&
ids.every((id) => typeof id === 'string' && /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/i.test(id))
);
} Try / catch
try { await api.fn(ids); } catch (e) { if (e?.code === 'InvalidArg' && /invalid '.*' at index/.test(e.message)) highlightBadEntry(e.message); throw e; } Prevention
- Normalize/validate all commit-id arrays at the UI boundary before crossing N-API.
- Prefer passing IDs obtained from the API itself rather than user-typed strings.
When it happens
Trigger: Calling an exported API that takes a list of commit IDs (e.g. Vec<ObjectId>) with one malformed entry: non-hex characters, wrong length (not 40/64 hex chars), empty string, or a full 'refs/...' name where a hash was expected.
Common situations: Frontend builds the array from user input or clipboard and includes a shortened-but-invalid hash; mixed data where a branch name slips into a commit-ID list; truncation elsewhere in the pipeline producing empty strings.
Related errors
- Cannot reconcile : branch name ' ' occurs more than once
- commit message cannot be empty
- Enter an Anthropic API key
- Enter an OpenAI API key
- is required
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/4f60e082a2ccf9a8.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-api-macros/src/lib.rs:1476
let #ident: gix::refs::FullName = gix::refs::FullName::try_from(#param_name)
.map_err(|e: gix::refs::name::Error| napi::Error::new(napi::Status::InvalidArg, format!("{e}")))?;
});
let call_ident = match &*pat_ty.ty {
syn::Type::Reference(_) => quote! { #ident.as_ref() },
_ => quote! { #ident },
};
call_arg_idents.push(call_ident);
} else if *last_ident == "Vec" && is_hex_hash_container_path(&mapping.json_ty, "Vec") {
// Vec<ObjectId> via Vec<HexHash> → Vec<String>, then parse each entry
params.push(quote! { #param_name: Vec<String> });
names.push(param_name.to_string());
conversions.push(quote! {
let #ident: Vec<gix::ObjectId> = #param_name
.into_iter()
.enumerate()
.map(|(index, value)| {
::std::str::FromStr::from_str(&value).map_err(|e: gix::hash::decode::Error| {
napi::Error::new(
napi::Status::InvalidArg,
format!("invalid '{}' at index {index}: {e}", stringify!(#param_name)),
)
})
})
.collect::<std::result::Result<Vec<_>, napi::Error>>()?;
});
let call_ident = match &*pat_ty.ty {
syn::Type::Reference(r) => {
let mutability = &r.mutability;
quote! { &#mutability #ident }
}
_ => quote! { #ident },
};
call_arg_idents.push(call_ident);
} else {
// Fallback: use serde_json::Value with ts_arg_type for proper TS typing
let ts_type_str = type_to_ts_name(&pat_ty.ty);View on GitHub (pinned to caf1f223d3)