{"record":{"id":"4369f72caf286f07","repo":"gitbutlerapp/gitbutler","slug":"existing-gitmeta-key-key-is-not-a-string","errorCode":null,"errorMessage":"existing GitMeta key '{key}' is not a string","messagePattern":"existing GitMeta key '(.+?)' is not a string","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-agentlog/src/projection.rs","lineNumber":754,"sourceCode":"                .then_with(|| lhs.model.cmp(&rhs.model))\n        });\n        labels.dedup();\n        output.insert(session_key.clone(), labels);\n    }\n    Ok(output)\n}\n\nfn optional_string_value(\n    handle: &git_meta_lib::SessionTargetHandle<'_>,\n    key: &str,\n) -> Result<Option<String>> {\n    match handle\n        .get_value(key)\n        .with_context(|| format!(\"failed to read GitMeta key '{key}'\"))?\n    {\n        None => Ok(None),\n        Some(MetaValue::String(value)) => Ok(Some(value)),\n        Some(_) => bail!(\"existing GitMeta key '{key}' is not a string\"),\n    }\n}\n\nfn session_handle(request: &ProjectionRequest, session_key: &str) -> String {\n    opaque_handle(\n        \"ps\",\n        &[\n            &request.pr.github_repository_id.to_string(),\n            &request.pr.pull_request.to_string(),\n            &request.snapshot.metadata_oid,\n            session_key,\n        ],\n    )\n}\n\nfn turn_handle(request: &ProjectionRequest, session_key: &str, turn_key: &str) -> String {\n    opaque_handle(\n        \"pt\",","sourceCodeStart":736,"sourceCodeEnd":772,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-agentlog/src/projection.rs#L736-L772","documentation":"`optional_string_value` is the projection's generic reader for per-source label keys — `:agent`, `:provider`, `:model`, and `:tool-version` under `gitbutler:agent-session:<key>:source:<source_key>:*`. It returns None for a missing key and Some for a String, but bails if the key exists with any other MetaValue variant. One malformed label key kills the projection for all requested sessions.","triggerScenarios":"Running a projection over sessions where any `<source_prefix>:agent|:provider|:model|:tool-version` key exists as a Set or List instead of a String — typically written by version-skewed writers or reshaped by conflicted merges.","commonSituations":"Mixed `but` versions writing source metadata; a GitMeta merge resolution that changed value types; manual edits while debugging source labels.","solutions":["Run `but agentlog sync` and retry the projection","Re-capture the affected sessions with the current `but` build to rewrite the label keys","Align `but` versions across contributing machines","Identify the malformed key (the error message names it) and restore its last String value from GitMeta history or delete it"],"exampleFix":"// before\nSome(_) => bail!(\"existing GitMeta key '{key}' is not a string\"),\n\n// after: skip malformed label keys (they are optional by design)\nmatch handle.get_value(key)? {\n    None => Ok(None),\n    Some(MetaValue::String(value)) => Ok(Some(value)),\n    Some(_) => Ok(None),\n}","handlingStrategy":"type-guard","validationCode":"// Pre-validate optional label keys before running the projection\nfor key in [\":agent\", \":provider\", \":model\", \":tool-version\"] {\n    if let Some(v) = handle.get_value(&format!(\"{source_prefix}{key}\"))? {\n        if !matches!(v, MetaValue::String(_)) { /* skip this label */ }\n    }\n}","typeGuard":"fn label_is_string(v: Option<&MetaValue>) -> bool {\n    matches!(v, Some(MetaValue::String(_)))\n}","tryCatchPattern":"match optional_string_value(&handle, &label_key) {\n    Ok(v) => v,\n    Err(err) if err.to_string().contains(\"is not a string\") => None,\n    Err(err) => return Err(err),\n}","preventionTips":["Treat label keys as optional metadata; never require them for pipeline success","Re-capture sessions after version upgrades to normalize label shapes","Avoid writing label keys from scripts or editors"],"tags":["gitmeta","type-mismatch","projection","labels","optional-fields","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"}