zeroclaw-labs/zeroclaw · error · anyhow::Error

peer group [ ] already exists but its channel ref is ` `…

Error message

peer group [{conventional_key}] already exists but its channel ref is `{}` (expected `{dotted_ref}`) — fix the group key or channel ref in config.toml before pairing

What it means

When no existing peer group carries this channel's dotted ref, merge_external_peer wants to create the conventional '<channel_type>_<alias>' key with channel = '<channel_type>.<alias>'. If that exact key is already taken by a group whose channel field points at a different ref, it bails rather than overwriting: writing there would store the identity where this channel's reader (Config::channel_external_peers, which matches on the channel field) never looks, while the other channel's reader would silently start authorizing it.

Solutions

  1. Rename the colliding group key in config.toml to something that does not equal <channel_type>_<alias> (e.g. telegram_main), keeping its channel field as-is
  2. Or, if the existing group was actually meant for this channel, fix its channel field to the expected dotted ref '<channel_type>.<alias>' shown in the error
  3. Or delete the stale group if it authorizes nothing, letting pairing recreate it in the conventional shape
  4. Re-run the pairing after the config edit — the merge is idempotent and will now find a clean path

Example fix

# before (config.toml)
[peer_groups.wechat_main]
channel = "telegram.main"

# after — rename the key so it no longer collides with the conventional wechat slot
[peer_groups.telegram_main]
channel = "telegram.main"
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight before pairing: detect the conventional-key collision merge would reject:
let conventional = format!("{channel_type}_{alias}");
if let Some(g) = cfg.peer_groups.get(&conventional) {
    anyhow::ensure!(
        g.channel.as_str() == format!("{channel_type}.{alias}"),
        "peer_groups[{conventional}] collides: channel={}", g.channel.as_str()
    );
}

Try / catch

match merge_external_peer(&mut cfg, channel_type, alias, identity) {
    Err(e) if e.to_string().contains("already exists but its channel ref") => {
        // surface exact expected/actual refs to the operator; block pairing until config fixed
    }
    other => other?,
}

Prevention

When it happens

Trigger: config.toml contains e.g. [peer_groups.wechat_main] with channel = "telegram.main" (hand-named group that collides with the conventional wechat key), and a WeChat pairing for alias 'main' then needs to create its conventional group. The bail reports the existing group's actual channel ref versus the expected dotted ref.

Common situations: Operator hand-rolled peer groups before the conventional naming existed; copy-pasted a group block and changed the channel field but not the key; two channel types whose names concatenate to the same conventional key.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/b801366591ba1a37. Report an issue: GitHub.

Appendix: source

Thrown at crates/zeroclaw-channels/src/identity_persist.rs:125

    };

    if let Some(key) = target_key {
        // Invariant: `target_key` was selected from existing map entries.
        if let Some(group) = cfg.peer_groups.get_mut(&key) {
            group
                .external_peers
                .push(PeerUsername::new(normalized.to_string()));
        }
        return Ok(true);
    }

    // No group carries this channel's dotted ref yet — create the
    // conventional shape. Refuse to squat on a key that belongs to a
    // different channel: writing there would put the identity where this
    // channel's reader never looks, while the *other* channel's reader
    // would silently start authorizing it.
    if let Some(existing) = cfg.peer_groups.get(&conventional_key) {
        anyhow::bail!(
            "peer group [{conventional_key}] already exists but its channel ref \
             is `{}` (expected `{dotted_ref}`) — fix the group key or channel ref \
             in config.toml before pairing",
            existing.channel.as_str()
        );
    }
    cfg.peer_groups.insert(
        conventional_key,
        PeerGroupConfig {
            channel: ChannelRef::new(dotted_ref),
            external_peers: vec![PeerUsername::new(normalized.to_string())],
            ..PeerGroupConfig::default()
        },
    );
    Ok(true)
}

/// Persist a paired identity as an authorized external peer.

View on GitHub (pinned to 88bb9c8533)