{"record":{"id":"202c5dee45570b4a","repo":"gitbutlerapp/gitbutler","slug":"existing-gitmeta-key-transcript-key-is-missing","errorCode":null,"errorMessage":"existing GitMeta key '{transcript_key}' is missing","messagePattern":"existing GitMeta key '(.+?)' is missing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-agentlog/src/gitmeta/read_support.rs","lineNumber":76,"sourceCode":"        return Ok(None);\n    };\n    let MetaValue::String(detail) = value else {\n        bail!(\"existing GitMeta key '{detail_key}' is not a string\");\n    };\n    serde_json::from_str(&detail)\n        .with_context(|| format!(\"existing GitMeta key '{detail_key}' has invalid JSON\"))\n        .map(Some)\n}\n\npub(super) fn read_transcript_entries(\n    handle: &SessionTargetHandle<'_>,\n    transcript_key: &str,\n) -> Result<Vec<ListEntry>> {\n    let Some(transcript_value) = handle\n        .get_value(transcript_key)\n        .with_context(|| format!(\"failed to read GitMeta key '{transcript_key}'\"))?\n    else {\n        bail!(\"existing GitMeta key '{transcript_key}' is missing\");\n    };\n    let MetaValue::List(transcript_entries) = transcript_value else {\n        bail!(\"existing GitMeta key '{transcript_key}' is not a list\");\n    };\n    Ok(transcript_entries)\n}\n\npub(super) fn transcript_records_by_hash<T>(\n    entries: Vec<ListEntry>,\n    needed_hashes: &std::collections::HashSet<String>,\n    mut parse_record: impl FnMut(&str) -> Option<(String, T)>,\n) -> HashMap<String, T> {\n    let mut records = HashMap::with_capacity(needed_hashes.len());\n    for entry in entries.into_iter().rev() {\n        let Some((record_hash, record)) = parse_record(&entry.value) else {\n            continue;\n        };\n        if !needed_hashes.contains(&record_hash) {","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-agentlog/src/gitmeta/read_support.rs#L58-L94","documentation":"`read_transcript_entries` reads the session's raw transcript under `<session_prefix>:transcript` and requires it to exist as a `MetaValue::List` — unlike the preview path in `session_outline.rs`, absence is fatal here. The bail fires when a session is expected to have a transcript but the key is missing entirely. That points to an incomplete publish, an older data format, or a partially synced state.","triggerScenarios":"Record-reassembly paths (`show`, record-hash lookups) on a session that has turns/summaries but no `:transcript` key — e.g. the session was published by a writer that did not store transcripts, or a pull/push cycle dropped it.","commonSituations":"Sessions captured before the transcript-storage feature; interrupted or conflicted GitMeta sync; a turn list rewritten without the matching transcript; deleting keys manually while debugging.","solutions":["Run `but agentlog sync` to complete the state and retry","Re-capture the session via the hook (new agent activity re-stores `:transcript`) and re-publish","If the original transcript file still exists locally (e.g. ~/.claude or ~/.codex session log), re-run capture from it","If the transcript is gone for good, treat the session as summary-only: delete or skip its transcript-dependent records"],"exampleFix":"// before\nlet entries = read_transcript_entries(&handle, &transcript_key)?;\n\n// after: check existence first and degrade gracefully\nlet entries = match handle.get_value(&transcript_key)? {\n    Some(MetaValue::List(entries)) => entries,\n    Some(_) => bail!(\"transcript key '{transcript_key}' has the wrong type\"),\n    None => { eprintln!(\"session has no stored transcript; skipping\"); Vec::new() }\n};","handlingStrategy":"validation","validationCode":"// Check transcript presence before record reassembly\nlet transcript_key = format!(\"{session_prefix}:transcript\");\nif handle.get_value(&transcript_key)?.is_none() {\n    eprintln!(\"no stored transcript for session; run sync or re-capture\");\n}","typeGuard":"fn transcript_present(handle: &SessionTargetHandle<'_>, key: &str) -> Result<bool> {\n    Ok(handle.get_value(key)?.is_some())\n}","tryCatchPattern":"match read_transcript_entries(&handle, &transcript_key) {\n    Ok(entries) => entries,\n    Err(err) if err.to_string().contains(\"is missing\") => Vec::new(),\n    Err(err) => return Err(err),\n}","preventionTips":["Treat interrupted publishes as incomplete: re-run publish rather than reading","Keep the original agent transcript files until sessions are confirmed synced","Re-capture old sessions before relying on transcript-dependent views"],"tags":["gitmeta","missing-key","transcript","partial-write","rust"],"backgroundTag":"missing-required-key","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}