gitbutlerapp/gitbutler · error
existing GitMeta key '{transcript_key}' is not a list
Error message
existing GitMeta key '{transcript_key}' is not a list What it means
The session transcript is stored as a `MetaValue::List` of timestamped entries under `<session_prefix>:transcript`. `read_transcript_entries` accepts a missing key as fatal (error 86) and a non-List variant as this error. It means the transcript key exists but holds a String or Set instead of the expected entry list.
Source
Thrown at crates/but-agentlog/src/gitmeta/read_support.rs:79
bail!("existing GitMeta key '{detail_key}' is not a string");
};
serde_json::from_str(&detail)
.with_context(|| format!("existing GitMeta key '{detail_key}' has invalid JSON"))
.map(Some)
}
pub(super) fn read_transcript_entries(
handle: &SessionTargetHandle<'_>,
transcript_key: &str,
) -> Result<Vec<ListEntry>> {
let Some(transcript_value) = handle
.get_value(transcript_key)
.with_context(|| format!("failed to read GitMeta key '{transcript_key}'"))?
else {
bail!("existing GitMeta key '{transcript_key}' is missing");
};
let MetaValue::List(transcript_entries) = transcript_value else {
bail!("existing GitMeta key '{transcript_key}' is not a list");
};
Ok(transcript_entries)
}
pub(super) fn transcript_records_by_hash<T>(
entries: Vec<ListEntry>,
needed_hashes: &std::collections::HashSet<String>,
mut parse_record: impl FnMut(&str) -> Option<(String, T)>,
) -> HashMap<String, T> {
let mut records = HashMap::with_capacity(needed_hashes.len());
for entry in entries.into_iter().rev() {
let Some((record_hash, record)) = parse_record(&entry.value) else {
continue;
};
if !needed_hashes.contains(&record_hash) {
continue;
}
records.insert(record_hash, record);View on GitHub (pinned to caf1f223d3)
Solutions
- Run `but agentlog sync` and retry
- Re-capture and re-publish the session so the transcript key is rewritten as a List
- Align `but` versions on all machines sharing the GitMeta remote
- Restore the last List-valued version from the GitMeta ref history, or delete the malformed key
Example fix
// before
let entries = read_transcript_entries(&handle, &transcript_key)?;
// after: narrow the variant explicitly
let entries = match handle.get_value(&transcript_key)? {
Some(MetaValue::List(entries)) => entries,
Some(_) => bail!("transcript key '{transcript_key}' is not a list; re-sync required"),
None => Vec::new(),
}; Defensive patterns
Strategy: type-guard
Validate before calling
// Confirm the transcript key holds a List before reassembly
if let Some(value) = handle.get_value(&transcript_key)? {
if !matches!(value, MetaValue::List(_)) { /* malformed; sync or re-capture */ }
} Type guard
fn transcript_is_list(v: Option<&MetaValue>) -> bool {
matches!(v, Some(MetaValue::List(_)))
} Try / catch
match read_transcript_entries(&handle, &transcript_key) {
Ok(e) => e,
Err(err) if err.to_string().contains("is not a list") => Vec::new(),
Err(err) => return Err(err),
} Prevention
- One but version per metadata remote
- Re-capture to rewrite transcripts after format changes
- Never reshape GitMeta values with external tooling
When it happens
Trigger: `show`/record reassembly on a session whose `:transcript` key was written as a non-List variant — typically version skew between but-agentlog writers or a mangled GitMeta merge.
Common situations: Mixed `but` versions capturing the same session; push-conflict resolution reshaping the value; manual key edits; restoring metadata from an incompatible backup.
Related errors
- existing GitMeta key '{transcript_key}' is not a list
- existing GitMeta key '{updated_at_key}' is not a string
- existing GitMeta key '{index_key}' is not a set
- existing GitMeta key '{associated_targets_key}' is not a str
- existing GitMeta key '{turns_key}' is not a list
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/d5f23f42de817198.
Report an issue: GitHub.