gitbutlerapp/gitbutler · error

existing GitMeta key

Error message

existing GitMeta key '{index_key}' is not a set

What it means

target_index_hits reads a GitMeta key holding the set of index hits for a target. An absent key means 'no hits yet' (empty set is returned), but if the key exists with a MetaValue other than Set, the stored data violates the expected shape and the function bails. This prevents misreading wrongly-typed stored values as index hits.

Solutions

  1. Delete the wrongly-typed key so the next write recreates it as a MetaValue::Set
  2. Find the writer producing the wrong variant and fix it to use set-add semantics
  3. Verify no two tool versions with different key schemas run against the same repo
  4. Rebuild the GitMeta index if corruption is widespread

Example fix

// before: writer stored wrong type
handle.set_value(&index_key, MetaValue::String(keys.join(",")));
// after: store as a set
for k in keys {
    handle.add_to_set(&index_key, k);
}
Defensive patterns

Strategy: validation

Validate before calling

let v = gitmeta_get(&repo_path, &index_key);
let ok = matches!(v, None | Some(MetaValue::Set(_)));
if !ok { /* key exists with wrong type; delete/repair before reading */ }

Type guard

fn is_set_or_absent(v: Option<&MetaValue>) -> bool {
    matches!(v, None | Some(MetaValue::Set(_)))
}

Try / catch

match target_index_hits(handle, key) {
    Ok(hits) => hits,
    Err(e) if e.to_string().contains("is not a set") => {
        handle.delete_value(&index_key); // repair
        BTreeSet::new()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: verified_related_turns_by_sessions -> target_index_hits when handle.get_value(index_key) returns Some(value) that is not MetaValue::Set — something overwrote the index key with a string/list value.

Common situations: Concurrent writers using different value shapes for the same key; a writer bug storing a list instead of a set; manual GitMeta manipulation; storage corruption; version drift between tools sharing the GitMeta namespace.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at crates/but-agentlog/src/gitmeta/read.rs:205

            }
        })
        .collect())
}

fn target_index_hits(
    handle: &SessionTargetHandle<'_>,
    target: RelatedTarget<'_>,
    status: PublicationStatus,
) -> Result<BTreeSet<String>> {
    let index_key = status_index_key(status, target.index_kind(), target.key());
    let Some(value) = handle
        .get_value(&index_key)
        .with_context(|| format!("failed to read GitMeta key '{index_key}'"))?
    else {
        return Ok(BTreeSet::new());
    };
    let MetaValue::Set(index_hits) = value else {
        bail!("existing GitMeta key '{index_key}' is not a set");
    };
    Ok(index_hits)
}

fn turn_detail_observes_target(
    handle: &SessionTargetHandle<'_>,
    hit: &IndexHit,
    target: RelatedTarget<'_>,
    status: PublicationStatus,
    session_activity_matches: &mut BTreeMap<String, bool>,
) -> Result<bool> {
    let detail_key = format!(
        "{}:turn:{}",
        session_storage_prefix(status, &hit.session_key),
        hit.turn_key
    );
    let Some(detail) = read_optional_turn_detail(handle, &detail_key)? else {
        return Ok(false);

View on GitHub (pinned to 58e5313667)