{"record":{"id":"4f60e082a2ccf9a8","repo":"gitbutlerapp/gitbutler","slug":"invalidarg","errorCode":"InvalidArg","errorMessage":"invalid '{}' at index {index}: {e}","messagePattern":"invalid '(.+?)' at index (.+?): (.+?)","errorType":"validation","errorClass":"napi::Error","httpStatus":null,"severity":"error","filePath":"crates/but-api-macros/src/lib.rs","lineNumber":1476,"sourceCode":"                    let #ident: gix::refs::FullName = gix::refs::FullName::try_from(#param_name)\n                        .map_err(|e: gix::refs::name::Error| napi::Error::new(napi::Status::InvalidArg, format!(\"{e}\")))?;\n                });\n                let call_ident = match &*pat_ty.ty {\n                    syn::Type::Reference(_) => quote! { #ident.as_ref() },\n                    _ => quote! { #ident },\n                };\n                call_arg_idents.push(call_ident);\n            } else if *last_ident == \"Vec\" && is_hex_hash_container_path(&mapping.json_ty, \"Vec\") {\n                // Vec<ObjectId> via Vec<HexHash> → Vec<String>, then parse each entry\n                params.push(quote! { #param_name: Vec<String> });\n                names.push(param_name.to_string());\n                conversions.push(quote! {\n                    let #ident: Vec<gix::ObjectId> = #param_name\n                        .into_iter()\n                        .enumerate()\n                        .map(|(index, value)| {\n                            ::std::str::FromStr::from_str(&value).map_err(|e: gix::hash::decode::Error| {\n                                napi::Error::new(\n                                    napi::Status::InvalidArg,\n                                    format!(\"invalid '{}' at index {index}: {e}\", stringify!(#param_name)),\n                                )\n                            })\n                        })\n                        .collect::<std::result::Result<Vec<_>, napi::Error>>()?;\n                });\n                let call_ident = match &*pat_ty.ty {\n                    syn::Type::Reference(r) => {\n                        let mutability = &r.mutability;\n                        quote! { &#mutability #ident }\n                    }\n                    _ => quote! { #ident },\n                };\n                call_arg_idents.push(call_ident);\n            } else {\n                // Fallback: use serde_json::Value with ts_arg_type for proper TS typing\n                let ts_type_str = type_to_ts_name(&pat_ty.ty);","sourceCodeStart":1458,"sourceCodeEnd":1494,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-api-macros/src/lib.rs#L1458-L1494","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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'."],"exampleFix":"// before\nawait api.someFn(['4c1f0ab...', 'oops']);\n\n// after\nconst isOid = (s: string) => /^[0-9a-f]{40,64}$/i.test(s);\nif (!ids.every(isOid)) throw new Error('invalid commit id in list');\nawait api.someFn(ids);","handlingStrategy":"validation","validationCode":"const OID_RE = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/i; // SHA-1 or SHA-256\nfunction assertOidList(ids: string[]): void {\n  ids.forEach((id, i) => {\n    if (!OID_RE.test(id)) throw new TypeError(`ids[${i}] is not a valid object id: ${id}`);\n  });\n}","typeGuard":"function isOidList(ids: unknown): ids is string[] {\n  return (\n    Array.isArray(ids) &&\n    ids.every((id) => typeof id === 'string' && /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/i.test(id))\n  );\n}","tryCatchPattern":"try { await api.fn(ids); } catch (e) { if (e?.code === 'InvalidArg' && /invalid '.*' at index/.test(e.message)) highlightBadEntry(e.message); throw e; }","preventionTips":["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."],"tags":["rust","napi","validation","object-id","macro-generated"],"backgroundTag":"invalid-hash-id","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}