gitbutlerapp/gitbutler · error

existing GitMeta key '{associated_targets_key}' is not a str

Error message

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

What it means

During publish, `session_target_associations` reads the session's `:associated-targets` key to merge new branch/review/change observations. A missing key yields defaults, but an existing key that is not a `MetaValue::String` (holding the TargetAssociations JSON) bails. This blocks the write path, so publishing cannot update associations for that session.

Source

Thrown at crates/but-agentlog/src/gitmeta/write.rs:510

                .iter()
                .map(|key| status_index_key(status, "change", key)),
        );
        keys
    }
}

fn session_target_associations(
    handle: &SessionTargetHandle<'_>,
    associated_targets_key: &str,
) -> Result<TargetAssociations> {
    let Some(value) = handle
        .get_value(associated_targets_key)
        .with_context(|| format!("failed to read GitMeta key '{associated_targets_key}'"))?
    else {
        return Ok(TargetAssociations::default());
    };
    let MetaValue::String(value) = value else {
        bail!("existing GitMeta key '{associated_targets_key}' is not a string");
    };
    serde_json::from_str::<TargetAssociations>(&value).with_context(|| {
        format!("existing GitMeta key '{associated_targets_key}' has invalid JSON")
    })
}

fn index_hits_for_turns(session_key: &str, turn_keys: &[String]) -> Result<Vec<String>> {
    turn_keys
        .iter()
        .map(|turn_key| {
            serde_json::to_string(&IndexHit {
                session_key: session_key.to_owned(),
                turn_key: turn_key.to_owned(),
            })
            .context("failed to serialize agentlog index hit")
        })
        .collect()
}

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Run `but agentlog sync` and retry publish
  2. Re-capture the session under the current `but` build so the associations key is rewritten as JSON-in-a-String
  3. Align versions across machines sharing the GitMeta remote
  4. Restore the last String-valued key from GitMeta history or delete it (absence is treated as default associations)

Example fix

// before
let MetaValue::String(value) = value else { bail!("...") };

// after: treat a malformed associations value as empty
let associations = match value {
    MetaValue::String(value) => serde_json::from_str::<TargetAssociations>(&value)?,
    _ => TargetAssociations::default(),
};
Defensive patterns

Strategy: validation

Validate before calling

// Before publish, validate the associations key's shape
if let Some(value) = handle.get_value(associated_targets_key)? {
    if !matches!(value, MetaValue::String(_)) { /* malformed; sync or re-capture first */ }
}

Type guard

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

Try / catch

match publish(&repo, &session) {
    Ok(()) => {}
    Err(err) if err.to_string().contains("associated-targets") => {
        eprintln!("associations key malformed; run sync, then re-publish")
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: `but agentlog publish <target>` on a session whose existing `:associated-targets` key holds a Set or List variant — typically written by an incompatible but-agentlog version or reshaped by a conflicted metadata merge.

Common situations: Mixed `but` versions writing the same session; push-conflict resolutions producing wrong shapes; manual GitMeta edits; incompatible restores.

Related errors


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