gitbutlerapp/gitbutler · error

existing GitMeta key '{detail_key}' is not a string

Error message

existing GitMeta key '{detail_key}' is not a string

What it means

Each turn's full detail is stored as JSON in a `MetaValue::String` under `<session_prefix>:turn:<turn_key>`. `read_optional_turn_detail` tolerates a missing key but bails when the key exists and is not a String. So this error means the detail record was stored with the wrong MetaValue variant, not that it is absent.

Source

Thrown at crates/but-agentlog/src/gitmeta/read_support.rs:61

) -> Result<StoredTurnDetail> {
    let Some(detail) = read_optional_turn_detail(handle, detail_key)? else {
        bail!("existing GitMeta key '{detail_key}' is missing");
    };
    Ok(detail)
}

pub(super) fn read_optional_turn_detail(
    handle: &SessionTargetHandle<'_>,
    detail_key: &str,
) -> Result<Option<StoredTurnDetail>> {
    let Some(value) = handle
        .get_value(detail_key)
        .with_context(|| format!("failed to read GitMeta key '{detail_key}'"))?
    else {
        return Ok(None);
    };
    let MetaValue::String(detail) = value else {
        bail!("existing GitMeta key '{detail_key}' is not a string");
    };
    serde_json::from_str(&detail)
        .with_context(|| format!("existing GitMeta key '{detail_key}' has invalid JSON"))
        .map(Some)
}

pub(super) fn read_transcript_entries(
    handle: &SessionTargetHandle<'_>,
    transcript_key: &str,
) -> Result<Vec<ListEntry>> {
    let Some(transcript_value) = handle
        .get_value(transcript_key)
        .with_context(|| format!("failed to read GitMeta key '{transcript_key}'"))?
    else {
        bail!("existing GitMeta key '{transcript_key}' is missing");
    };
    let MetaValue::List(transcript_entries) = transcript_value else {
        bail!("existing GitMeta key '{transcript_key}' is not a list");

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Run `but agentlog sync` and retry the show command
  2. Re-capture and re-publish the session to rewrite all `:turn:*` detail keys in the current format
  3. Align `but` versions across machines sharing the GitMeta remote
  4. Inspect the GitMeta ref history for the exact `:turn:<turn_key>` key and restore the last String-valued version, or delete it

Example fix

// before
let detail = read_optional_turn_detail(&handle, &detail_key)?;

// after: validate the variant before deserializing
let detail = match handle.get_value(detail_key)? {
    None => None,
    Some(MetaValue::String(s)) => Some(serde_json::from_str(&s)?),
    Some(_) => bail!("turn detail key '{detail_key}' has an unexpected type; re-sync"),
};
Defensive patterns

Strategy: type-guard

Validate before calling

// Narrow the variant before parsing turn detail
match handle.get_value(detail_key)? {
    Some(MetaValue::String(raw)) => { let _detail: StoredTurnDetail = serde_json::from_str(&raw)?; }
    Some(_) => { /* wrong shape: skip or repair */ }
    None => { /* absent */ }
}

Type guard

fn turn_detail_is_string(v: Option<&MetaValue>) -> bool {
    matches!(v, Some(MetaValue::String(_)))
}

Try / catch

match read_optional_turn_detail(&handle, &detail_key) {
    Ok(Some(d)) => d,
    Ok(None) => Default::default(),
    Err(err) if err.to_string().contains("is not a string") => {
        eprintln!("skipping malformed turn detail {detail_key}")
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: `but agentlog show <session> --turn <turn_key>` hitting a detail key stored as a Set or List — typically after version-skewed writers, corrupted GitMeta state, or a bad merge of the metadata ref.

Common situations: Mixed `but` versions writing the same session; push-conflict resolutions that reshaped values; manual GitMeta edits; sessions restored from a backup made by an incompatible build.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/7a9fd73c71b77518. Report an issue: GitHub.