{"record":{"id":"a55e955bb0fb07e4","repo":"gitbutlerapp/gitbutler","slug":"existing-gitmeta-key-updated-at-key-is-not-a-s","errorCode":null,"errorMessage":"existing GitMeta key '{updated_at_key}' is not a string","messagePattern":"existing GitMeta key '(.+?)' is not a string","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-agentlog/src/gitmeta/read.rs","lineNumber":63,"sourceCode":"            | RelatedTarget::Change(key) => key,\n        }\n    }\n}\n\nfn session_list_entry(\n    handle: &SessionTargetHandle<'_>,\n    status: PublicationStatus,\n    session_key: String,\n) -> Result<SessionListEntry> {\n    let session_prefix = session_storage_prefix(status, &session_key);\n    let updated_at_key = format!(\"{session_prefix}:updated-at\");\n    let updated_at = match handle\n        .get_value(&updated_at_key)\n        .with_context(|| format!(\"failed to read GitMeta key '{updated_at_key}'\"))?\n    {\n        None => bail!(\"existing session '{session_key}' is missing updated-at\"),\n        Some(MetaValue::String(updated_at)) => updated_at,\n        Some(_) => bail!(\"existing GitMeta key '{updated_at_key}' is not a string\"),\n    };\n    let sort_updated_at = DateTime::parse_from_rfc3339(&updated_at)\n        .with_context(|| format!(\"existing GitMeta key '{updated_at_key}' has invalid timestamp\"))?\n        .with_timezone(&Utc);\n    Ok(SessionListEntry {\n        session_key,\n        status,\n        updated_at,\n        sort_updated_at,\n    })\n}\n\npub(crate) fn find_related_sessions_limited(\n    repo_path: &Path,\n    target: RelatedTarget<'_>,\n    max_sessions: Option<usize>,\n) -> Result<Vec<RelatedSession>> {\n    find_related_sessions_limited_by_statuses(","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-agentlog/src/gitmeta/read.rs#L45-L81","documentation":"but-agentlog persists every agent session in GitMeta (a git-backed, remotely-synced key-value store) under keys like `gitbutler:agent-session:<status>:<key>:updated-at`. When building the session list, `session_list_entry` requires that key to be a `MetaValue::String` holding an RFC 3339 timestamp. If the key exists but holds a different MetaValue variant (Set or List), this bail fires and aborts the whole listing.","triggerScenarios":"Any session-listing path (`but agentlog show <session>`, skim discovery, related-session lookups) iterating a session whose `:updated-at` key was written as a Set or List instead of a String — e.g. after version skew between two but-agentlog writers, or a foreign/manual write to the GitMeta ref.","commonSituations":"Upgrading `but` binaries that changed the value encoding; two machines syncing metadata from different versions; hand-edits or tooling writing raw GitMeta keys; a push-conflict resolution that re-imported older-format keys.","solutions":["Run `but agentlog sync` to pull a consistent metadata state and retry the command","Identify which writer version produced the key (inspect the GitMeta ref's git history) and align all machines on one `but` version, then re-publish the session","Delete or rewrite the single malformed `gitbutler:agent-session:<status>:<key>:updated-at` key so the session either heals on next capture or is skipped","If the session is dispensable, remove all of its `gitbutler:agent-session:<key>:*` keys so listing no longer trips over it"],"exampleFix":"// before: corrupt key breaks the entire listing\nlet entry = session_list_entry(&handle, status, session_key)?;\n\n// after: skip sessions whose updated-at has the wrong shape\nlet entry = match session_list_entry(&handle, status, session_key) {\n    Ok(entry) => entry,\n    Err(err) if err.to_string().contains(\"is not a string\") => continue,\n    Err(err) => return Err(err),\n};","handlingStrategy":"type-guard","validationCode":"// Before listing, verify the updated-at key's variant\nlet key = format!(\"gitbutler:agent-session:{status}:{session_key}:updated-at\");\nmatch handle.get_value(&key)? {\n    Some(MetaValue::String(_)) => { /* safe to call the listing API */ }\n    Some(_) => { /* skip session; wrong shape */ }\n    None => { /* session not fully stored */ }\n}","typeGuard":"fn updated_at_is_string(v: Option<&MetaValue>) -> bool {\n    matches!(v, Some(MetaValue::String(_)))\n}","tryCatchPattern":"match session_list_entry(&handle, status, key) {\n    Ok(entry) => entries.push(entry),\n    Err(err) if err.to_string().contains(\"is not a string\") => {\n        eprintln!(\"skipping session {key}: malformed updated-at\");\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Run `but agentlog sync` before listing sessions after switching machines","Keep one `but` version across all machines sharing a GitMeta remote","Never hand-edit GitMeta keys; use but-agentlog commands only","After any but downgrade/upgrade, re-publish sessions so keys are rewritten"],"tags":["gitmeta","type-mismatch","session-storage","rust"],"backgroundTag":"stored-data-type-mismatch","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}