gitbutlerapp/gitbutler · error
existing GitMeta key
Error message
existing GitMeta key '{transcript_key}' is not a list What it means
session_previews in crates/but-agentlog/src/gitmeta/session_outline.rs reads a transcript list from GitMeta and expects the stored MetaValue to be a List of turn entries. When the key exists but holds a different value type (String, Set, etc.), the code bails with this message because it cannot sort/iterate the entries. This indicates the GitMeta key was written by a different code path or an older/incompatible schema version.
Solutions
- Inspect the GitMeta key value and delete/rewrite it so it is a List of transcript entries (e.g. via but meta tooling or git meta branch checkout).
- Verify the writer version: re-record the session transcript with the current but-agentlog so the key is written as a List.
- If hit during preview only, regenerate session outline data by re-running the recording/ingest for that session.
Example fix
// before
let MetaValue::List(entries) = value else {
bail!("existing GitMeta key '{transcript_key}' is not a list");
};
// after (tolerate a JSON-encoded string)
let entries = match value {
MetaValue::List(entries) => entries,
MetaValue::String(s) => serde_json::from_str(&s)?,
other => bail!("existing GitMeta key '{transcript_key}' has unexpected type: {other:?}"),
}; Defensive patterns
Strategy: validation
Validate before calling
// Rust: before relying on previews, check the stored shape
let value = meta.get_value(&transcript_key)?;
if let Some(v) = &value {
if !matches!(v, MetaValue::List(_)) {
// treat as stale: delete or migrate the key before reading
meta.delete_value(&transcript_key)?;
}
} Type guard
fn is_list(v: &MetaValue) -> bool { matches!(v, MetaValue::List(_)) } Try / catch
match session_outline(...).context("reading session previews") {
Ok(p) => p,
Err(e) if e.to_string().contains("is not a list") => SessionPreviews::default(),
Err(e) => return Err(e),
} Prevention
- Never write GitMeta transcript keys by hand; always use the agentlog writer APIs.
- Keep all machines writing to the repo on the same agentlog version.
- Treat GitMeta key shapes as schema: document and validate expected MetaValue variants per key prefix.
When it happens
Trigger: Calling related_session_outline -> session_previews when the GitMeta key '{transcript_key}' exists via get_value but its MetaValue variant is not MetaValue::List.
Common situations: A hand-edited or externally written GitMeta entry; an older schema that stored transcripts as a JSON string; a key collision where another tool wrote a Set or plain value under the same transcript key.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- existing GitMeta key
- existing GitMeta key
- existing GitMeta key
- existing GitMeta key
- existing GitMeta key
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/f5032bb9b5ff81d7.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-agentlog/src/gitmeta/session_outline.rs:90
fn session_previews(
handle: &SessionTargetHandle<'_>,
session_prefix: &str,
preview_hashes: &HashSet<String>,
) -> Result<SessionPreviews> {
if preview_hashes.is_empty() {
return Ok(SessionPreviews::default());
}
let transcript_key = format!("{session_prefix}:transcript");
let Some(value) = handle
.get_value(&transcript_key)
.with_context(|| format!("failed to read GitMeta key '{transcript_key}'"))?
else {
return Ok(SessionPreviews::default());
};
let MetaValue::List(entries) = value else {
bail!("existing GitMeta key '{transcript_key}' is not a list");
};
let mut previews = SessionPreviews::default();
let mut entries = entries;
entries.sort_by_key(|entry| entry.timestamp);
for entry in entries.into_iter().rev() {
if previews.latest_user.is_some() && previews.latest_assistant.is_some() {
break;
}
let Ok(record) = serde_json::from_str::<Value>(&entry.value) else {
continue;
};
let Some(record_hash) = record["record_hash"].as_str() else {
continue;
};
if !preview_hashes.contains(record_hash) {
continue;View on GitHub (pinned to 58e5313667)