{"record":{"id":"2f6e1e0f71163d3e","repo":"gitbutlerapp/gitbutler","slug":"existing-gitmeta-key-sources-key-is-not-a-set","errorCode":null,"errorMessage":"existing GitMeta key '{sources_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/projection.rs","lineNumber":714,"sourceCode":"\nfn session_source_metadata<'a>(\n    repo_path: &Path,\n    session_keys: impl IntoIterator<Item = &'a String>,\n) -> Result<BTreeMap<String, Vec<ProjectionAgentLabel>>> {\n    let gitmeta = Session::open(repo_path).context(\"failed to open GitMeta session\")?;\n    let target = Target::project();\n    let handle = gitmeta.target(&target);\n    let mut output = BTreeMap::new();\n    for session_key in session_keys {\n        let session_prefix = format!(\"gitbutler:agent-session:{session_key}\");\n        let sources_key = format!(\"{session_prefix}:sources\");\n        let sources = match handle\n            .get_value(&sources_key)\n            .with_context(|| format!(\"failed to read GitMeta key '{sources_key}'\"))?\n        {\n            None => BTreeSet::new(),\n            Some(MetaValue::Set(values)) => values.into_iter().collect(),\n            Some(_) => bail!(\"existing GitMeta key '{sources_key}' is not a set\"),\n        };\n        let mut labels = Vec::new();\n        for source_key in sources {\n            let source_prefix = format!(\"{session_prefix}:source:{source_key}\");\n            let label = ProjectionAgentLabel {\n                agent: optional_string_value(&handle, &format!(\"{source_prefix}:agent\"))?,\n                provider: optional_string_value(&handle, &format!(\"{source_prefix}:provider\"))?,\n                model: optional_string_value(&handle, &format!(\"{source_prefix}:model\"))?,\n                tool_version: optional_string_value(\n                    &handle,\n                    &format!(\"{source_prefix}:tool-version\"),\n                )?,\n            };\n            if label.agent.is_some() || label.provider.is_some() || label.model.is_some() {\n                labels.push(label);\n            }\n        }\n        labels.sort_by(|lhs, rhs| {","sourceCodeStart":696,"sourceCodeEnd":732,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-agentlog/src/projection.rs#L696-L732","documentation":"The PR projection reads each session's `:sources` key (e.g. `gitbutler:agent-session:<key>:sources`) as a `MetaValue::Set` of source identifiers, then reads per-source label keys. A missing key means no sources; an existing key that is not a Set bails, aborting the whole projection for the requested sessions.","triggerScenarios":"Building a projection over sessions where one session's `:sources` key holds a String or List — e.g. state written by an incompatible but-agentlog version or mangled via a metadata merge.","commonSituations":"Mixed `but` versions across contributor machines pushing to the same GitMeta remote; projections run over sessions captured by pre-release builds; manual key edits.","solutions":["Run `but agentlog sync` and retry the projection","Re-capture the affected sessions under the current `but` build so `:sources` is rewritten as a Set","Align `but` versions across all machines contributing agentlog data","Restore the last Set-valued key from GitMeta history or delete it (absence reads as 'no sources')"],"exampleFix":"// before\nSome(_) => bail!(\"existing GitMeta key '{sources_key}' is not a set\"),\n\n// after: treat a malformed sources key as no sources\nlet sources = match handle.get_value(&sources_key)? {\n    Some(MetaValue::Set(values)) => values.into_iter().collect(),\n    _ => BTreeSet::new(),\n};","handlingStrategy":"type-guard","validationCode":"// Before projecting, verify each session's sources key variant\nif let Some(value) = handle.get_value(&sources_key)? {\n    if !matches!(value, MetaValue::Set(_)) { /* skip session in this projection */ }\n}","typeGuard":"fn sources_is_set(v: Option<&MetaValue>) -> bool {\n    matches!(v, Some(MetaValue::Set(_)))\n}","tryCatchPattern":"let sources = match handle.get_value(&sources_key)? {\n    Some(MetaValue::Set(values)) => values.into_iter().collect(),\n    _ => BTreeSet::new(),\n};","preventionTips":["Project only over sessions captured by the current but generation","Sync before running projections on shared repos","Keep source keys but-agentlog-owned; no external writers"],"tags":["gitmeta","type-mismatch","sources","projection","rust"],"backgroundTag":"stored-data-type-mismatch","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}