gitbutlerapp/gitbutler · error

existing GitMeta key '{index_key}' is not a set

Error message

existing GitMeta key '{index_key}' is not a set

What it means

but-agentlog keeps per-status index keys (built by `status_index_key`, one per branch/review/change target) whose value must be a `MetaValue::Set` of JSON-serialized `IndexHit` entries pointing at sessions. When reading the index for a related target, a value of any other MetaValue variant triggers this bail. It signals the index key exists but was stored with the wrong shape.

Source

Thrown at crates/but-agentlog/src/gitmeta/read.rs:205

            }
        })
        .collect())
}

fn target_index_hits(
    handle: &SessionTargetHandle<'_>,
    target: RelatedTarget<'_>,
    status: PublicationStatus,
) -> Result<BTreeSet<String>> {
    let index_key = status_index_key(status, target.index_kind(), target.key());
    let Some(value) = handle
        .get_value(&index_key)
        .with_context(|| format!("failed to read GitMeta key '{index_key}'"))?
    else {
        return Ok(BTreeSet::new());
    };
    let MetaValue::Set(index_hits) = value else {
        bail!("existing GitMeta key '{index_key}' is not a set");
    };
    Ok(index_hits)
}

fn turn_detail_observes_target(
    handle: &SessionTargetHandle<'_>,
    hit: &IndexHit,
    target: RelatedTarget<'_>,
    status: PublicationStatus,
    session_activity_matches: &mut BTreeMap<String, bool>,
) -> Result<bool> {
    let detail_key = format!(
        "{}:turn:{}",
        session_storage_prefix(status, &hit.session_key),
        hit.turn_key
    );
    let Some(detail) = read_optional_turn_detail(handle, &detail_key)? else {
        return Ok(false);

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Run `but agentlog sync` and retry the skim/publish — a clean pull often replaces the malformed index
  2. Align all machines on one `but` version so one writer shape is used, then re-publish the target to rewrite the index key
  3. Inspect the GitMeta ref history for the index key and restore the last version that was a Set
  4. Delete the malformed index key (listings degrade to 'no related sessions' until re-published) rather than aborting every read

Example fix

// before
let hits = index_hits(&handle, target, status)?; // bails on wrong variant

// after: treat a wrongly-shaped index as empty
let hits = match index_hits(&handle, target, status) {
    Ok(hits) => hits,
    Err(err) if err.to_string().contains("is not a set") => BTreeSet::new(),
    Err(err) => return Err(err),
};
Defensive patterns

Strategy: type-guard

Validate before calling

// Pre-check the index key's variant before skim/publish
let index_key = status_index_key(status, kind, target_key);
if let Some(value) = handle.get_value(&index_key)? {
    if !matches!(value, MetaValue::Set(_)) {
        // malformed index; skip or repair before calling skim
    }
}

Type guard

fn index_is_set(v: &MetaValue) -> bool {
    matches!(v, MetaValue::Set(_))
}

Try / catch

match index_hits(&handle, target, status) {
    Ok(hits) => process(hits),
    Err(err) if err.to_string().contains("is not a set") => {
        eprintln!("index for target is malformed; treating as empty")
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: `but agentlog skim branch <name>` / `skim review <id>` / `skim change <id>`, or `publish <target>`, reading `status_index_key(status, kind, key)` when that key holds a String or List instead of a Set of IndexHit JSON strings.

Common situations: Index key written by an older/newer but-agentlog with a different encoding; GitMeta push-conflict resolutions merging keys of mixed shapes; manual edits or third-party tools writing the index key; partial migration leaving a stale index value.

Related errors


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