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

  1. Run `but agentlog sync` and retry
  2. Re-capture and re-publish the session so the transcript key is rewritten as a List
  3. Align `but` versions on all machines sharing the GitMeta remote
  4. 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

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


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/d5f23f42de817198. Report an issue: GitHub.