{"record":{"id":"b801366591ba1a37","repo":"zeroclaw-labs/zeroclaw","slug":"peer-group-conventional-key-already-exists-but","errorCode":null,"errorMessage":"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","messagePattern":"peer group \\[(.+?)\\] already exists but its channel ref is `(.+?)` \\(expected `(.+?)`\\) — fix the group key or channel ref in config\\.toml before pairing","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/identity_persist.rs","lineNumber":125,"sourceCode":"    };\n\n    if let Some(key) = target_key {\n        // Invariant: `target_key` was selected from existing map entries.\n        if let Some(group) = cfg.peer_groups.get_mut(&key) {\n            group\n                .external_peers\n                .push(PeerUsername::new(normalized.to_string()));\n        }\n        return Ok(true);\n    }\n\n    // No group carries this channel's dotted ref yet — create the\n    // conventional shape. Refuse to squat on a key that belongs to a\n    // different channel: writing there would put the identity where this\n    // channel's reader never looks, while the *other* channel's reader\n    // would silently start authorizing it.\n    if let Some(existing) = cfg.peer_groups.get(&conventional_key) {\n        anyhow::bail!(\n            \"peer group [{conventional_key}] already exists but its channel ref \\\n             is `{}` (expected `{dotted_ref}`) — fix the group key or channel ref \\\n             in config.toml before pairing\",\n            existing.channel.as_str()\n        );\n    }\n    cfg.peer_groups.insert(\n        conventional_key,\n        PeerGroupConfig {\n            channel: ChannelRef::new(dotted_ref),\n            external_peers: vec![PeerUsername::new(normalized.to_string())],\n            ..PeerGroupConfig::default()\n        },\n    );\n    Ok(true)\n}\n\n/// Persist a paired identity as an authorized external peer.","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/identity_persist.rs#L107-L143","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","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","Or delete the stale group if it authorizes nothing, letting pairing recreate it in the conventional shape","Re-run the pairing after the config edit — the merge is idempotent and will now find a clean path"],"exampleFix":"# before (config.toml)\n[peer_groups.wechat_main]\nchannel = \"telegram.main\"\n\n# after — rename the key so it no longer collides with the conventional wechat slot\n[peer_groups.telegram_main]\nchannel = \"telegram.main\"","handlingStrategy":"validation","validationCode":"// Pre-flight before pairing: detect the conventional-key collision merge would reject:\nlet conventional = format!(\"{channel_type}_{alias}\");\nif let Some(g) = cfg.peer_groups.get(&conventional) {\n    anyhow::ensure!(\n        g.channel.as_str() == format!(\"{channel_type}.{alias}\"),\n        \"peer_groups[{conventional}] collides: channel={}\", g.channel.as_str()\n    );\n}","typeGuard":null,"tryCatchPattern":"match merge_external_peer(&mut cfg, channel_type, alias, identity) {\n    Err(e) if e.to_string().contains(\"already exists but its channel ref\") => {\n        // surface exact expected/actual refs to the operator; block pairing until config fixed\n    }\n    other => other?,\n}","preventionTips":["Adopt the conventional <type>_<alias> key / channel = \"<type>.<alias>\" shape for hand-written peer groups","Run a config-lint rule: no peer group key may equal a conventional key of a different channel's dotted ref"],"tags":["identity-persist","peer-groups","configuration","config-toml"],"backgroundTag":"config-key-conflict","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}