gitbutlerapp/gitbutler · error

Error when registering schema

Error message

Error when registering schema {} for type {} registered at {}

Duplicate type {} was registered at {}. Consider renaming one of the types with either `#[serde(rename = ...)]` or `#[schemars(rename = ...)]`.

What it means

During TypeScript binding generation, `collect_all_schemas` in but-ts registers schemas for all types collected from the source crates. Two distinct types ended up with the same registered schema name (typically a struct/enum name), which would produce conflicting `$ref` entries in the generated schema. The tool aborts with `bail!` and points at both registration locations.

Solutions

  1. Add `#[serde(rename = "UniqueName")]` and/or `#[schemars(rename = "UniqueName")]` to one of the conflicting types so the registered names differ.
  2. Un-register the duplicate type if it was added by mistake (remove its `register_sdk_type!()` call).
  3. Check the two `registration_location` paths in the message to find both declarations and decide which one to rename.

Example fix

// before
#[derive(JsonSchema)]
pub struct Branch { pub name: String }
// after
#[derive(JsonSchema)]
#[serde(rename = "ButlerBranch")]
#[schemars(rename = "ButlerBranch")]
pub struct Branch { pub name: String }
Defensive patterns

Strategy: validation

Validate before calling

let names: HashSet<_> = registered.iter().map(|t| t.name.as_str()).collect();
if names.len() != registered.len() {
    return Err("duplicate schema names found before generating bindings");
}

Prevention

When it happens

Trigger: Running `but-ts` when two types in the source crates (or a type and a dependent definition) share the same `#[schemars]`/derived name, e.g. two structs named `Branch` in different modules that are both registered via `register_sdk_type!()`.

Common situations: Renaming a module and keeping a same-named struct; adding a new SDK type without noticing an existing registration with the same Rust type name; generating across multiple `--source-crates` where names collide.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at crates/but-ts/src/main.rs:415

    // types we actually intend to output.  This avoids false name-collision
    // errors when unrelated crates happen to register a type with the same
    // schema name (e.g. two different `Author` types from different crates).
    if !source_crates.is_empty() {
        types.retain(|t| {
            source_crates
                .iter()
                .any(|prefix| t.type_name.starts_with(prefix.as_str()))
        });
    }

    // Order by registration location for a stable checking order
    types.sort_by_cached_key(|s| s.registration_location);

    let mut existing_names = HashMap::<&str, &CollectedSchemarEntry>::new();

    for t in &types {
        if let Some(duplicate) = existing_names.get(t.name.as_ref()) {
            bail!(
                "Error when registering schema {} for type {} registered at {}\n\nDuplicate type {} was registered at {}. Consider renaming one of the types with either `#[serde(rename = ...)]` or `#[schemars(rename = ...)]`.\n",
                t.name,
                t.type_name,
                t.registration_location,
                duplicate.type_name,
                duplicate.registration_location
            );
        } else {
            existing_names.insert(t.name.as_ref(), t);
        }
    }

    for t in &types {
        if let Some(obj) = t.schema.as_object()
            && let Some(serde_json::Value::Object(defs)) =
                obj.get("$defs").or_else(|| obj.get("definitions"))
        {
            for (def_name, def_schema) in defs {

View on GitHub (pinned to 58e5313667)