gitbutlerapp/gitbutler · error · napi::Error

invalid ' ' at index

Error message

invalid '{}' at index {index}: {e}

What it means

This error is generated by the but_api NAPI macro when converting a Vec<String> parameter back into Vec<gix::ObjectId> for a GitButler API call. One of the strings in the array is not a valid Git object ID (40-char SHA-1 or 64-char SHA-256 hex). The inner gix::hash::decode::Error is wrapped into a napi::Error with Status::InvalidArg, naming the parameter and the failing array index.

Solutions

  1. Print and inspect the array element at the reported index and fix it to a full valid hex object ID
  2. Verify the object actually exists (git rev-parse <value>) before calling the API
  3. Check that you are not passing branch/ref names where object IDs are required
  4. Ensure consistent hash length (40 vs 64 chars) matching the repository's object format

Example fix

// before
api.commitFiles(['abc123', '9f8e7d...']);  // truncated id
// after
api.commitFiles(['9f8e7d6c5b4a3210fedcba9876543210abcdef12']);  // full 40-char hex
Defensive patterns

Strategy: validation

Validate before calling

function isValidObjectId(s) { return typeof s === 'string' && /^[0-9a-f]{40}$|^[0-9a-f]{64}$/.test(s); }
ids.forEach((id, i) => { if (!isValidObjectId(id)) throw new Error(`bad object id at index ${i}: ${id}`); });

Type guard

const isObjectId = (v: unknown): v is string => typeof v === 'string' && /^[0-9a-f]{40}([0-9a-f]{24})?$/.test(v);

Try / catch

try { api.commitFiles(ids); } catch (e) { if (String(e.message).includes('invalid')) { const idx = /index (\d+)/.exec(e.message); console.error('bad id at', idx?.[1]); } throw e; }

Prevention

When it happens

Trigger: Calling any generated but_api NAPI function whose parameter maps to Vec<gix::ObjectId> (via maps_vec_object_id_to_vec_string_napi_param_and_parser) with an array containing a string that fails gix::hash::from_str decoding — e.g. wrong length, non-hex characters, or an empty string in the array.

Common situations: JS/TS callers pass truncated commit IDs from UI copy/paste, pass branch names instead of object IDs, pass IDs from a different hash algorithm, or include null/undefined coerced to 'null'/'undefined' strings inside the array.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

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

                                    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| {
                                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 58e5313667)