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
- Run `but agentlog sync` and retry the show command
- Re-run the capture hook for that session (new agent activity rewrites `:turns`) or re-publish the session
- Align `but` versions on every machine sharing the GitMeta remote
- 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
- Let the capture hook finish writing before reading a session (avoid racing hook completion)
- Sync before show when switching machines
- Align but versions across writers of the same session
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
- existing GitMeta key '{turns_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 '{detail_key}' is not a string
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/79a50fe2b5e9ef27.
Report an issue: GitHub.