{"record":{"id":"040469a08a2b5dfe","repo":"gitbutlerapp/gitbutler","slug":"existing-gitmeta-key-turns-key-is-not-a-list-040469","errorCode":null,"errorMessage":"existing GitMeta key '{turns_key}' is not a list","messagePattern":"existing GitMeta key '(.+?)' is not a list","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-agentlog/src/gitmeta/write.rs","lineNumber":81,"sourceCode":"    let handle = gitmeta.target(&target);\n    let publication_status = capture_publication_status(repo_path, &handle, session_key)?;\n    let session_prefix = session_storage_prefix(publication_status, session_key);\n    let sources_key = format!(\"{session_prefix}:sources\");\n    let source_prefix = format!(\"{session_prefix}:source:{source_key}\");\n    let transcript_key = format!(\"{session_prefix}:transcript\");\n    let record_hashes_key = format!(\"{session_prefix}:record-hashes\");\n    let turns_key = format!(\"{session_prefix}:turns\");\n    let associated_targets_key = format!(\"{session_prefix}:associated-targets\");\n\n    let turns_value = handle\n        .get_value(&turns_key)\n        .with_context(|| format!(\"failed to read GitMeta key '{turns_key}'\"))?;\n    let previous_turns = match turns_value.as_ref() {\n        None => Vec::new(),\n        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) {","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-agentlog/src/gitmeta/write.rs#L63-L99","documentation":"When appending new capture records, the write path first reads the existing `<session_prefix>:turns` value to merge turn summaries; `None` is fine (new session), a List is parsed, and any other variant bails. So this error occurs on the write/append side, meaning the previously stored turns entry has a shape the current writer cannot merge.","triggerScenarios":"The agentlog capture hook or publish appending records to a session whose existing `:turns` key holds a non-List value — e.g. the session was last written by an incompatible but-agentlog version or the key was corrupted.","commonSituations":"Downgrading or mixing `but` versions on the same repository/metadata remote; GitMeta conflict resolution that reshaped the value; sessions written by pre-release builds; manual key tampering.","solutions":["Run `but agentlog sync`, then retry the capture/publish","Re-capture the session under the current `but` version so `:turns` is rewritten in List form","Align all machines/hooks on one `but` build","Restore the last List-valued `:turns` from GitMeta ref history, or delete the key so the writer starts a fresh turn list"],"exampleFix":"// before\nlet previous_turns = match turns_value.as_ref() {\n    None => Vec::new(),\n    Some(MetaValue::List(entries)) => stored_turn_summary_entries(entries.to_vec(), &turns_key)?,\n    Some(_) => bail!(\"existing GitMeta key '{turns_key}' is not a list\"),\n};\n\n// after: quarantine a malformed turns list instead of blocking capture\nlet previous_turns = match turns_value.as_ref() {\n    None => Vec::new(),\n    Some(MetaValue::List(entries)) => stored_turn_summary_entries(entries.to_vec(), &turns_key)?,\n    Some(_) => {\n        warn!(\"turns key '{turns_key}' malformed; starting a fresh turn list\");\n        Vec::new()\n    }\n};","handlingStrategy":"validation","validationCode":"// Before appending records, validate the existing turns entry's shape\nmatch handle.get_value(&turns_key)? {\n    None | Some(MetaValue::List(_)) => { /* safe to append */ }\n    Some(_) => { eprintln!(\"existing turns key malformed; sync or re-capture first\"); }\n}","typeGuard":"fn appendable_turns(v: Option<&MetaValue>) -> bool {\n    matches!(v, None | Some(MetaValue::List(_)))\n}","tryCatchPattern":"match append_records(&handle, &records) {\n    Ok(()) => {}\n    Err(err) if err.to_string().contains(\"turns\") && err.to_string().contains(\"not a list\") => {\n        eprintln!(\"session turns malformed; run sync, then re-capture\")\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Keep hook and CLI on the same but build as the machine that created the session","Never downgrade but below the version that wrote a session","After a failed append, run sync before retrying capture"],"tags":["gitmeta","type-mismatch","turns","write-path","capture","rust"],"backgroundTag":"stored-data-type-mismatch","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}