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

  1. Check the entry at the reported index and fix or remove it; the message names both the parameter and index.
  2. Validate every entry client-side with /^[0-9a-f]{40}$/i (or 64 for SHA-256 repos) before calling the API.
  3. 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

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


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)