{"record":{"id":"e531a52e5b858495","repo":"gitbutlerapp/gitbutler","slug":"existing-gitmeta-key-record-hashes-key-is-not","errorCode":null,"errorMessage":"existing GitMeta key '{record_hashes_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/write.rs","lineNumber":96,"sourceCode":"        Some(MetaValue::List(entries)) => {\n            stored_turn_summary_entries(entries.to_vec(), &turns_key)?\n        }\n        Some(_) => bail!(\"existing GitMeta key '{turns_key}' is not a list\"),\n    };\n    let previous_turn_key = previous_turns\n        .last()\n        .map(|turn| turn.summary.turn_key.to_owned());\n    let incoming_record_hashes = records\n        .iter()\n        .map(|record| record.source_record_hash.clone())\n        .collect::<Vec<_>>();\n    let mut seen_hashes = match handle\n        .get_value(&record_hashes_key)\n        .with_context(|| format!(\"failed to read GitMeta key '{record_hashes_key}'\"))?\n    {\n        None => HashSet::new(),\n        Some(MetaValue::Set(hashes)) => hashes.into_iter().collect(),\n        Some(_) => bail!(\"existing GitMeta key '{record_hashes_key}' is not a set\"),\n    };\n    records.retain(|record| {\n        if seen_hashes.contains(&record.source_record_hash) {\n            false\n        } else {\n            seen_hashes.insert(record.source_record_hash.clone());\n            true\n        }\n    });\n\n    if records.is_empty() {\n        let metadata_changed = enrich_incomplete_turn(\n            &handle,\n            turns_value,\n            session_key,\n            source_key,\n            &incoming_record_hashes,\n            publication_status,","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-agentlog/src/gitmeta/write.rs#L78-L114","documentation":"The write path deduplicates incoming records against a `<session_prefix>:record-hashes` key that must be a `MetaValue::Set` of already-seen record hashes; `None` means a fresh set. If the key exists with any other variant, appending bails. This stops duplicate records but also stops all capture for that session until the key is fixed.","triggerScenarios":"Hook capture or publish appending records to a session whose existing `:record-hashes` key holds a String or List — e.g. written by an incompatible writer version or reshaped by a metadata merge.","commonSituations":"Mixed `but` versions writing one session; a conflicted GitMeta sync resolved into the wrong shape; manual edits; restoring from an incompatible backup.","solutions":["Run `but agentlog sync` and retry the operation","Re-capture the session with the current `but` build to rewrite `:record-hashes` as a Set","Align versions across machines and hooks","Restore the last Set-valued version from GitMeta history, or delete the key (cost: records may be re-stored once until dedup state rebuilds)"],"exampleFix":"// before\nlet mut seen_hashes = match handle.get_value(&record_hashes_key)? {\n    None => HashSet::new(),\n    Some(MetaValue::Set(hashes)) => hashes.into_iter().collect(),\n    Some(_) => bail!(\"existing GitMeta key '{record_hashes_key}' is not a set\"),\n};\n\n// after: reset dedup state on a malformed key\nlet mut seen_hashes = match handle.get_value(&record_hashes_key)? {\n    Some(MetaValue::Set(hashes)) => hashes.into_iter().collect(),\n    _ => HashSet::new(),\n};","handlingStrategy":"validation","validationCode":"// Before publish, confirm the record-hashes key is a Set\nif let Some(value) = handle.get_value(&record_hashes_key)? {\n    if !matches!(value, MetaValue::Set(_)) { /* malformed; sync or delete key */ }\n}","typeGuard":"fn dedup_state_is_set(v: Option<&MetaValue>) -> bool {\n    matches!(v, Some(MetaValue::Set(_)))\n}","tryCatchPattern":"match publish_records(&handle, &records) {\n    Ok(()) => {}\n    Err(err) if err.to_string().contains(\"record-hashes\") => {\n        eprintln!(\"dedup state malformed; run sync and retry — dedup rebuilds safely\")\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Keep record-hash keys owned by but-agentlog only","Re-run publish after sync; dedup loss only risks one duplicate record, not data loss","Align but versions across machines writing the same session"],"tags":["gitmeta","type-mismatch","record-hashes","dedup","write-path","rust"],"backgroundTag":"stored-data-type-mismatch","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}