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
- Delete the wrongly-typed key so the next write recreates it as a MetaValue::Set
- Find the writer producing the wrong variant and fix it to use set-add semantics
- Verify no two tool versions with different key schemas run against the same repo
- 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
- Use set-add APIs for index keys, never raw set_value with other variants
- Fix writers that store comma-joined strings or lists in index keys
- Avoid mixing tool versions that share GitMeta keyspaces
- Periodically validate key types across the GitMeta store
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
- existing GitMeta key
- existing GitMeta key
- existing GitMeta key
- existing GitMeta key
- existing GitMeta key
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)