gitbutlerapp/gitbutler · error · napi::Error

invalid

Error message

invalid '{}': {e}

What it means

Generated in `build_napi_params` for optional ObjectId parameters (`Option<gix::ObjectId>` taken as `Option<String>`): when a value is provided, `FromStr` into `gix::ObjectId` fails with `gix::hash::decode::Error` and the error message is prefixed `invalid '<paramName>': {e}` as napi InvalidArg. Like error 867 but for optional parameters and naming the offending parameter.

Solutions

  1. Pass undefined/null for the parameter when there is no id, rather than an empty or placeholder string.
  2. Supply the full 40-char lowercase hex object id when a value is present.
  3. Resolve short hashes to full ids before the call.
  4. Use the parameter name in the message (`invalid '<name>': ...`) to locate exactly which argument is malformed.

Example fix

// before
await api.getLog(baseId ?? ""); // empty string fails decode
// after
await api.getLog(baseId ?? undefined);
Defensive patterns

Strategy: type-guard

Validate before calling

const HASH_RE = /^[0-9a-f]{40}$/;
function toOptionalObjectId(id) {
  if (id === undefined || id === null) return undefined; // let the API see "absent"
  if (typeof id !== 'string' || !HASH_RE.test(id)) {
    throw new TypeError(`invalid commit id: ${id}`);
  }
  return id;
}

Type guard

function isPresentObjectId(v) { return v !== undefined && v !== null && /^[0-9a-f]{40}$/.test(v); }

Try / catch

try { await api.getLog(idOpt); }
catch (e) {
  const m = /invalid '([^']+)':/.exec(e?.message ?? '');
  if (m) throw new Error(`argument ${m[1]} is not a valid object id`);
  throw e;
}

Prevention

When it happens

Trigger: Calling a `but_api` function with an optional commit/id argument set to a non-hex, wrong-length, or otherwise undecodable string instead of null/undefined.

Common situations: Optional ids defaulted to `""` or `"null"` strings instead of undefined; short hashes from UI selections; stale cached ids with corrupted characters.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/3c42532d53bcd67f. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-api-macros/src/lib.rs:1558

                    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 == "Option"
                && is_hex_hash_container_path(&mapping.json_ty, "Option")
            {
                // Option<ObjectId> via Option<HexHash> → Option<String>, then parse when present.
                params.push(quote! { #param_name: Option<String> });
                names.push(param_name.to_string());
                conversions.push(quote! {
                    let #ident: Option<gix::ObjectId> = #param_name
                        .map(|value| {
                            ::std::str::FromStr::from_str(&value).map_err(|e: gix::hash::decode::Error| {
                                napi::Error::new(
                                    napi::Status::InvalidArg,
                                    format!("invalid '{}': {e}", stringify!(#param_name)),
                                )
                            })
                        })
                        .transpose()?;
                });
                call_arg_idents.push(quote! { #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| {

View on GitHub (pinned to 58e5313667)