gitbutlerapp/gitbutler · error
existing GitMeta key '{updated_at_key}' is not a string
Error message
existing GitMeta key '{updated_at_key}' is not a string What it means
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.
Source
Thrown at crates/but-agentlog/src/gitmeta/read.rs:63
| RelatedTarget::Change(key) => key,
}
}
}
fn session_list_entry(
handle: &SessionTargetHandle<'_>,
status: PublicationStatus,
session_key: String,
) -> Result<SessionListEntry> {
let session_prefix = session_storage_prefix(status, &session_key);
let updated_at_key = format!("{session_prefix}:updated-at");
let updated_at = match handle
.get_value(&updated_at_key)
.with_context(|| format!("failed to read GitMeta key '{updated_at_key}'"))?
{
None => bail!("existing session '{session_key}' is missing updated-at"),
Some(MetaValue::String(updated_at)) => updated_at,
Some(_) => bail!("existing GitMeta key '{updated_at_key}' is not a string"),
};
let sort_updated_at = DateTime::parse_from_rfc3339(&updated_at)
.with_context(|| format!("existing GitMeta key '{updated_at_key}' has invalid timestamp"))?
.with_timezone(&Utc);
Ok(SessionListEntry {
session_key,
status,
updated_at,
sort_updated_at,
})
}
pub(crate) fn find_related_sessions_limited(
repo_path: &Path,
target: RelatedTarget<'_>,
max_sessions: Option<usize>,
) -> Result<Vec<RelatedSession>> {
find_related_sessions_limited_by_statuses(View on GitHub (pinned to caf1f223d3)
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
Example fix
// before: corrupt key breaks the entire listing
let entry = session_list_entry(&handle, status, session_key)?;
// after: skip sessions whose updated-at has the wrong shape
let entry = match session_list_entry(&handle, status, session_key) {
Ok(entry) => entry,
Err(err) if err.to_string().contains("is not a string") => continue,
Err(err) => return Err(err),
}; Defensive patterns
Strategy: type-guard
Validate before calling
// Before listing, verify the updated-at key's variant
let key = format!("gitbutler:agent-session:{status}:{session_key}:updated-at");
match handle.get_value(&key)? {
Some(MetaValue::String(_)) => { /* safe to call the listing API */ }
Some(_) => { /* skip session; wrong shape */ }
None => { /* session not fully stored */ }
} Type guard
fn updated_at_is_string(v: Option<&MetaValue>) -> bool {
matches!(v, Some(MetaValue::String(_)))
} Try / catch
match session_list_entry(&handle, status, key) {
Ok(entry) => entries.push(entry),
Err(err) if err.to_string().contains("is not a string") => {
eprintln!("skipping session {key}: malformed updated-at");
}
Err(err) => return Err(err),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- existing GitMeta key '{index_key}' is not a set
- existing GitMeta key '{associated_targets_key}' is not a str
- existing GitMeta key '{turns_key}' is not a list
- existing GitMeta key '{detail_key}' is not a string
- existing GitMeta key '{transcript_key}' is not a list
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/a55e955bb0fb07e4.
Report an issue: GitHub.