fix(cli): 1.31.6 — resolve hex prefix to full pubkey before send so messages actually deliver
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

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:
Alejandro Gutiérrez
2026-05-04 16:45:09 +01:00
parent a852a9df18
commit 25586d298f
3 changed files with 78 additions and 1 deletions

View File

@@ -1,5 +1,29 @@
# Changelog # 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 ## 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 Two follow-ups after 1.31.4 made the human renderer show role/groups

View File

@@ -1,6 +1,6 @@
{ {
"name": "claudemesh-cli", "name": "claudemesh-cli",
"version": "1.31.5", "version": "1.31.6",
"description": "Peer mesh for Claude Code sessions — CLI + MCP server.", "description": "Peer mesh for Claude Code sessions — CLI + MCP server.",
"keywords": [ "keywords": [
"claude-code", "claude-code",

View File

@@ -47,6 +47,59 @@ export async function runSend(flags: SendFlags, to: string, message: string): Pr
flags.mesh ?? flags.mesh ??
(config.meshes.length === 1 ? config.meshes[0]!.slug : null); (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 // 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 // caller's own member pubkey (or any of the caller's session/member
// entries), refuse without --self. Catches the common pasted-from- // entries), refuse without --self. Catches the common pasted-from-