{"record":{"id":"7a9fd73c71b77518","repo":"gitbutlerapp/gitbutler","slug":"existing-gitmeta-key-detail-key-is-not-a-strin","errorCode":null,"errorMessage":"existing GitMeta key '{detail_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/gitmeta/read_support.rs","lineNumber":61,"sourceCode":") -> Result<StoredTurnDetail> {\n    let Some(detail) = read_optional_turn_detail(handle, detail_key)? else {\n        bail!(\"existing GitMeta key '{detail_key}' is missing\");\n    };\n    Ok(detail)\n}\n\npub(super) fn read_optional_turn_detail(\n    handle: &SessionTargetHandle<'_>,\n    detail_key: &str,\n) -> Result<Option<StoredTurnDetail>> {\n    let Some(value) = handle\n        .get_value(detail_key)\n        .with_context(|| format!(\"failed to read GitMeta key '{detail_key}'\"))?\n    else {\n        return Ok(None);\n    };\n    let MetaValue::String(detail) = value else {\n        bail!(\"existing GitMeta key '{detail_key}' is not a string\");\n    };\n    serde_json::from_str(&detail)\n        .with_context(|| format!(\"existing GitMeta key '{detail_key}' has invalid JSON\"))\n        .map(Some)\n}\n\npub(super) fn read_transcript_entries(\n    handle: &SessionTargetHandle<'_>,\n    transcript_key: &str,\n) -> Result<Vec<ListEntry>> {\n    let Some(transcript_value) = handle\n        .get_value(transcript_key)\n        .with_context(|| format!(\"failed to read GitMeta key '{transcript_key}'\"))?\n    else {\n        bail!(\"existing GitMeta key '{transcript_key}' is missing\");\n    };\n    let MetaValue::List(transcript_entries) = transcript_value else {\n        bail!(\"existing GitMeta key '{transcript_key}' is not a list\");","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-agentlog/src/gitmeta/read_support.rs#L43-L79","documentation":"Each turn's full detail is stored as JSON in a `MetaValue::String` under `<session_prefix>:turn:<turn_key>`. `read_optional_turn_detail` tolerates a missing key but bails when the key exists and is not a String. So this error means the detail record was stored with the wrong MetaValue variant, not that it is absent.","triggerScenarios":"`but agentlog show <session> --turn <turn_key>` hitting a detail key stored as a Set or List — typically after version-skewed writers, corrupted GitMeta state, or a bad merge of the metadata ref.","commonSituations":"Mixed `but` versions writing the same session; push-conflict resolutions that reshaped values; manual GitMeta edits; sessions restored from a backup made by an incompatible build.","solutions":["Run `but agentlog sync` and retry the show command","Re-capture and re-publish the session to rewrite all `:turn:*` detail keys in the current format","Align `but` versions across machines sharing the GitMeta remote","Inspect the GitMeta ref history for the exact `:turn:<turn_key>` key and restore the last String-valued version, or delete it"],"exampleFix":"// before\nlet detail = read_optional_turn_detail(&handle, &detail_key)?;\n\n// after: validate the variant before deserializing\nlet detail = match handle.get_value(detail_key)? {\n    None => None,\n    Some(MetaValue::String(s)) => Some(serde_json::from_str(&s)?),\n    Some(_) => bail!(\"turn detail key '{detail_key}' has an unexpected type; re-sync\"),\n};","handlingStrategy":"type-guard","validationCode":"// Narrow the variant before parsing turn detail\nmatch handle.get_value(detail_key)? {\n    Some(MetaValue::String(raw)) => { let _detail: StoredTurnDetail = serde_json::from_str(&raw)?; }\n    Some(_) => { /* wrong shape: skip or repair */ }\n    None => { /* absent */ }\n}","typeGuard":"fn turn_detail_is_string(v: Option<&MetaValue>) -> bool {\n    matches!(v, Some(MetaValue::String(_)))\n}","tryCatchPattern":"match read_optional_turn_detail(&handle, &detail_key) {\n    Ok(Some(d)) => d,\n    Ok(None) => Default::default(),\n    Err(err) if err.to_string().contains(\"is not a string\") => {\n        eprintln!(\"skipping malformed turn detail {detail_key}\")\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Re-publish sessions after but-agentlog upgrades to rewrite detail keys","Avoid manual GitMeta edits","Verify with sync before reading turn details from another machine"],"tags":["gitmeta","type-mismatch","turn-detail","json","rust"],"backgroundTag":"stored-data-type-mismatch","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}