fix(cli): 1.31.6 — resolve hex prefix to full pubkey before send so messages actually deliver
claudemesh send <16-hex-prefix> would ack with sent to <prefix> (daemon) but the recipient never received the message. Broker pre-flight and the drain query both exact-match on full 64-char pubkey, so a prefix queued successfully but no recipient drain ever fetched the row. Sender saw sent, recipient saw nothing — silent drop. Fix: CLI resolves any hex prefix (4-63 chars, not full 64) to the full pubkey via the daemon peer list before submitting. Outcomes: - unique match: canonicalize and continue - no match: clear error + list of online peer display names - multiple: clear error + candidate list + hint to lengthen prefix The 16-hex prefix shown in peer list rows is now safe to paste straight into claudemesh send. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## 1.31.6 (2026-05-04) — hex-prefix sends actually deliver now
|
||||
|
||||
`claudemesh send <16-hex-prefix> "..."` would acknowledge with `sent
|
||||
to <prefix> (daemon)` but the recipient never received the message.
|
||||
The broker's pre-flight matched `peer.pubkey === targetSpec` and the
|
||||
drain query matched `target_spec = <full-pubkey>` — both exact-equal
|
||||
checks, so a 16-hex prefix queued successfully but no recipient drain
|
||||
ever fetched the row. Sender saw "sent", recipient saw nothing.
|
||||
|
||||
Fix: the CLI now resolves any hex prefix (4-63 chars, not full 64) to
|
||||
the full pubkey via the daemon's peer list before submitting to the
|
||||
broker. Three outcomes:
|
||||
|
||||
- **Unique match:** prefix is canonicalized to the full 64-char
|
||||
pubkey; the rest of the send pipeline is unchanged.
|
||||
- **No match:** clear error `No peer matches hex prefix "X"` with the
|
||||
list of online peers' display names.
|
||||
- **Multiple matches:** clear error listing the candidates and a hint
|
||||
to lengthen the prefix.
|
||||
|
||||
The 16-hex prefix shown in `peer list` rows is now safe to copy-paste
|
||||
into `claudemesh send` — what worked in the docs finally works in the
|
||||
CLI.
|
||||
|
||||
## 1.31.5 (2026-05-04) — JSON peer list lifts profile.role to top-level + skill guides LLMs to render it
|
||||
|
||||
Two follow-ups after 1.31.4 made the human renderer show role/groups
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claudemesh-cli",
|
||||
"version": "1.31.5",
|
||||
"version": "1.31.6",
|
||||
"description": "Peer mesh for Claude Code sessions — CLI + MCP server.",
|
||||
"keywords": [
|
||||
"claude-code",
|
||||
|
||||
@@ -47,6 +47,59 @@ export async function runSend(flags: SendFlags, to: string, message: string): Pr
|
||||
flags.mesh ??
|
||||
(config.meshes.length === 1 ? config.meshes[0]!.slug : null);
|
||||
|
||||
// 1.31.6: hex-prefix resolution. If `to` looks like hex but isn't a
|
||||
// full 64-char pubkey, resolve it against the peer list and replace
|
||||
// it with the matching full pubkey. The broker stores `targetSpec`
|
||||
// verbatim and the drain query at apps/broker/src/broker.ts:2408
|
||||
// matches only on full pubkeys, so a 16-hex prefix would queue
|
||||
// successfully but never fetch — sender saw "sent", recipient saw
|
||||
// nothing. Resolving here makes the CLI's prefix UX work end-to-end
|
||||
// and surfaces ambiguous / unmatched prefixes with a clear error
|
||||
// instead of a silent drop.
|
||||
if (
|
||||
!to.startsWith("@") &&
|
||||
!to.startsWith("#") &&
|
||||
to !== "*" &&
|
||||
/^[0-9a-f]{4,63}$/i.test(to)
|
||||
) {
|
||||
try {
|
||||
const { tryListPeersViaDaemon } = await import("~/services/bridge/daemon-route.js");
|
||||
const peers = (await tryListPeersViaDaemon()) ?? [];
|
||||
const lower = to.toLowerCase();
|
||||
const matches = peers.filter((p) => {
|
||||
const pk = (p as { pubkey?: string }).pubkey ?? "";
|
||||
const mpk = (p as { memberPubkey?: string }).memberPubkey ?? "";
|
||||
return pk.toLowerCase().startsWith(lower) || mpk.toLowerCase().startsWith(lower);
|
||||
});
|
||||
if (matches.length === 0) {
|
||||
render.err(`No peer matches hex prefix "${to}".`);
|
||||
const names = peers
|
||||
.map((p) => (p as { displayName?: string }).displayName)
|
||||
.filter(Boolean)
|
||||
.join(", ");
|
||||
if (names) render.hint(`online: ${names}`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (matches.length > 1) {
|
||||
const candidates = matches
|
||||
.map((p) => {
|
||||
const pk = (p as { pubkey?: string }).pubkey ?? "";
|
||||
const dn = (p as { displayName?: string }).displayName ?? "?";
|
||||
return `${dn} ${pk.slice(0, 16)}…`;
|
||||
})
|
||||
.join(", ");
|
||||
render.err(`Ambiguous hex prefix "${to}" — matches ${matches.length} peers.`);
|
||||
render.hint(`candidates: ${candidates}`);
|
||||
render.hint("Use a longer prefix or paste the full 64-char pubkey.");
|
||||
process.exit(1);
|
||||
}
|
||||
to = (matches[0] as { pubkey?: string }).pubkey ?? to;
|
||||
} catch {
|
||||
// Daemon unreachable — fall through; cold path will try a name
|
||||
// lookup and surface its own error if that also fails.
|
||||
}
|
||||
}
|
||||
|
||||
// Self-DM safety check: if target is a 64-char hex that matches the
|
||||
// caller's own member pubkey (or any of the caller's session/member
|
||||
// entries), refuse without --self. Catches the common pasted-from-
|
||||
|
||||
Reference in New Issue
Block a user