{"record":{"id":"fad74aa46bd80bdc","repo":"gitbutlerapp/gitbutler","slug":"existing-gitmeta-key-index-key-is-not-a-set","errorCode":null,"errorMessage":"existing GitMeta key '{index_key}' is not a set","messagePattern":"existing GitMeta key '(.+?)' is not a set","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-agentlog/src/gitmeta/read.rs","lineNumber":205,"sourceCode":"            }\n        })\n        .collect())\n}\n\nfn target_index_hits(\n    handle: &SessionTargetHandle<'_>,\n    target: RelatedTarget<'_>,\n    status: PublicationStatus,\n) -> Result<BTreeSet<String>> {\n    let index_key = status_index_key(status, target.index_kind(), target.key());\n    let Some(value) = handle\n        .get_value(&index_key)\n        .with_context(|| format!(\"failed to read GitMeta key '{index_key}'\"))?\n    else {\n        return Ok(BTreeSet::new());\n    };\n    let MetaValue::Set(index_hits) = value else {\n        bail!(\"existing GitMeta key '{index_key}' is not a set\");\n    };\n    Ok(index_hits)\n}\n\nfn turn_detail_observes_target(\n    handle: &SessionTargetHandle<'_>,\n    hit: &IndexHit,\n    target: RelatedTarget<'_>,\n    status: PublicationStatus,\n    session_activity_matches: &mut BTreeMap<String, bool>,\n) -> Result<bool> {\n    let detail_key = format!(\n        \"{}:turn:{}\",\n        session_storage_prefix(status, &hit.session_key),\n        hit.turn_key\n    );\n    let Some(detail) = read_optional_turn_detail(handle, &detail_key)? else {\n        return Ok(false);","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-agentlog/src/gitmeta/read.rs#L187-L223","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: writer stored wrong type\nhandle.set_value(&index_key, MetaValue::String(keys.join(\",\")));\n// after: store as a set\nfor k in keys {\n    handle.add_to_set(&index_key, k);\n}","handlingStrategy":"validation","validationCode":"let v = gitmeta_get(&repo_path, &index_key);\nlet ok = matches!(v, None | Some(MetaValue::Set(_)));\nif !ok { /* key exists with wrong type; delete/repair before reading */ }","typeGuard":"fn is_set_or_absent(v: Option<&MetaValue>) -> bool {\n    matches!(v, None | Some(MetaValue::Set(_)))\n}","tryCatchPattern":"match target_index_hits(handle, key) {\n    Ok(hits) => hits,\n    Err(e) if e.to_string().contains(\"is not a set\") => {\n        handle.delete_value(&index_key); // repair\n        BTreeSet::new()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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"],"tags":["gitmeta","type-mismatch","index","schema"],"backgroundTag":"type-mismatch","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}