gitbutlerapp/gitbutler · error

existing GitMeta key '{turns_key}' is not a list

Error message

existing GitMeta key '{turns_key}' is not a list

What it means

A session's turns are stored under `<session_prefix>:turns` as a `MetaValue::List` of summary entries. `read_turn_summaries` bails with this message when the key exists but is a String or Set instead of a List. Note the read path tolerates a missing key (returns empty) — only the wrong variant is fatal.

Source

Thrown at crates/but-agentlog/src/gitmeta/read_support.rs:29

) -> Result<T> {
    let gitmeta = Session::open(repo_path).context("failed to open GitMeta session")?;
    let project = Target::project();
    let handle = gitmeta.target(&project);
    read(&handle)
}

pub(super) fn read_turn_summaries(
    handle: &SessionTargetHandle<'_>,
    turns_key: &str,
) -> Result<Vec<StoredTurnSummary>> {
    let Some(turns_value) = handle
        .get_value(turns_key)
        .with_context(|| format!("failed to read GitMeta key '{turns_key}'"))?
    else {
        return Ok(Vec::new());
    };
    let MetaValue::List(turn_entries) = turns_value else {
        bail!("existing GitMeta key '{turns_key}' is not a list");
    };

    let summaries = stored_turn_summary_entries(turn_entries, turns_key)?
        .into_iter()
        .map(|entry| entry.summary)
        .collect::<Vec<_>>();

    Ok(summaries)
}

pub(super) fn read_turn_detail(
    handle: &SessionTargetHandle<'_>,
    detail_key: &str,
) -> Result<StoredTurnDetail> {
    let Some(detail) = read_optional_turn_detail(handle, detail_key)? else {
        bail!("existing GitMeta key '{detail_key}' is missing");
    };
    Ok(detail)

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Run `but agentlog sync` and retry the show command
  2. Re-run the capture hook for that session (new agent activity rewrites `:turns`) or re-publish the session
  3. Align `but` versions on every machine sharing the GitMeta remote
  4. Inspect the GitMeta ref history for `gitbutler:agent-session:*:turns` and restore or delete the malformed entry

Example fix

// before
let turns = read_turn_summaries(&handle, &turns_key)?;

// after: degrade a malformed turns list to empty
let turns = match read_turn_summaries(&handle, &turns_key) {
    Ok(t) => t,
    Err(err) if err.to_string().contains("is not a list") => Vec::new(),
    Err(err) => return Err(err),
};
Defensive patterns

Strategy: type-guard

Validate before calling

// Before show, confirm the turns key is a List
if let Some(value) = handle.get_value(&format!("{session_prefix}:turns"))? {
    if !matches!(value, MetaValue::List(_)) {
        eprintln!("session turns malformed; run `but agentlog sync`");
    }
}

Type guard

fn turns_is_list(v: Option<&MetaValue>) -> bool {
    matches!(v, Some(MetaValue::List(_)))
}

Try / catch

match read_turn_summaries(&handle, &turns_key) {
    Ok(t) => t,
    Err(err) if err.to_string().contains("is not a list") => Vec::new(),
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: `but agentlog show <session>` (and outline builders) reading the `:turns` key of a session whose turns were stored in a non-List variant — typically after version-skewed writes or corrupted metadata.

Common situations: Two agents on different `but` versions writing the same session; GitMeta push-conflict resolutions that mangled the turns entry; sessions created by a much older format that stored turns differently; manual key edits.

Related errors


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