gitbutlerapp/gitbutler · error

existing GitMeta key '{detail_key}' is missing

Error message

existing GitMeta key '{detail_key}' is missing

What it means

`read_turn_detail` requires the per-turn detail key `<session_prefix>:turn:<turn_key>` to exist; unlike its `read_optional_turn_detail` sibling it does not tolerate absence. The bail fires when the turns list or index references a turn whose detail record is not present in GitMeta. That indicates a torn or partially-synced state between the turn list and the turn detail records.

Source

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

    };
    let MetaValue::List(turn_entries) = turns_value else {
        bail!("existing GitMeta key '{turns_key}' is not a list");
    };

    let summaries = stored_turn_summary_entries(turn_entries, turns_key)?
        .into_iter()
        .map(|entry| entry.summary)
        .collect::<Vec<_>>();

    Ok(summaries)
}

pub(super) fn read_turn_detail(
    handle: &SessionTargetHandle<'_>,
    detail_key: &str,
) -> 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)

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Run `but agentlog sync` to complete the partial state and retry the exact turn key
  2. Re-run capture for the session (let the agent produce one more turn, then hook + publish) so details are rewritten
  3. Verify the turn key from `show <session>` output and retry with the exact `--turn` value — stale keys from an older turns list are a common cause
  4. If the detail is unrecoverable, delete the stale entry from the `:turns` list or the whole session and re-capture

Example fix

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

// after: prefer the optional reader and handle absence explicitly
let detail = match read_optional_turn_detail(&handle, &detail_key)? {
    Some(detail) => detail,
    None => bail!("turn '{detail_key}' has no stored detail; re-run `but agentlog sync`")
};
Defensive patterns

Strategy: validation

Validate before calling

// Verify the turn detail key exists before requesting it
let detail_key = format!("{session_prefix}:turn:{turn_key}");
if handle.get_value(&detail_key)?.is_none() {
    eprintln!("turn {turn_key} detail not synced yet; run `but agentlog sync`");
}

Type guard

fn turn_detail_exists(handle: &SessionTargetHandle<'_>, key: &str) -> Result<bool> {
    Ok(handle.get_value(key)?.is_some())
}

Try / catch

match read_optional_turn_detail(&handle, &detail_key)? {
    Some(detail) => detail,
    None => { /* sync and retry, or surface 'turn detail unavailable' */ }
}

Prevention

When it happens

Trigger: `but agentlog show <session> --turn <turn_key>` (and record-reassembly paths) where `:turns` lists the turn but `:turn:<turn_key>` is absent — e.g. a publish that wrote the turns list but not every detail key, or a partial pull/sync.

Common situations: Interrupted publish or sync (network dropped mid-push); GitMeta push-conflict resolution that dropped detail keys; reading a session captured by an older writer that did not persist turn details; concurrent capture and publish racing on the same session.

Related errors


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