fix(broker,cli): member-DM policy counts real sessions; a rejection is final
Found in the local E2E drill: - the broker counted connections, not sessions — a reattaching session briefly holds two sockets, and it logged live_sessions=3 for a member with 2. Count distinct session keys, excluding control-plane. - in reject mode the daemon retried member_target_requires_session like a transient error; it is now classified permanent (row → dead after one attempt, both daemon and session sockets). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2509,13 +2509,20 @@ async function handleSend(
|
||||
// so on the wire this only comes from older clients. Policy:
|
||||
// CLAUDEMESH_MEMBER_DM_POLICY=warn (default: log + count) | reject.
|
||||
{
|
||||
const liveSessionsOfTarget = [...connections.values()].filter(
|
||||
(peer) =>
|
||||
peer.meshId === conn.meshId &&
|
||||
peer.memberPubkey === msg.targetSpec &&
|
||||
!!peer.sessionPubkey &&
|
||||
peer.sessionPubkey !== msg.targetSpec,
|
||||
).length;
|
||||
// Distinct session keys — a reattaching session can briefly hold
|
||||
// two sockets, and the daemon's control-plane socket isn't a session.
|
||||
const liveSessionsOfTarget = new Set(
|
||||
[...connections.values()]
|
||||
.filter(
|
||||
(peer) =>
|
||||
peer.meshId === conn.meshId &&
|
||||
peer.memberPubkey === msg.targetSpec &&
|
||||
peer.peerRole !== "control-plane" &&
|
||||
!!peer.sessionPubkey &&
|
||||
peer.sessionPubkey !== msg.targetSpec,
|
||||
)
|
||||
.map((peer) => peer.sessionPubkey),
|
||||
).size;
|
||||
if (liveSessionsOfTarget > 0) {
|
||||
const policy = process.env.CLAUDEMESH_MEMBER_DM_POLICY === "reject" ? "reject" : "warn";
|
||||
log.warn("member_target_dm", {
|
||||
|
||||
@@ -524,5 +524,5 @@ function defaultLog(level: "info" | "warn" | "error", msg: string, meta?: Record
|
||||
|
||||
/** Heuristic: which broker errors are unrecoverable for this id. */
|
||||
function classifyPermanent(err: string): boolean {
|
||||
return /payload_too_large|forbidden|not_found|invalid|schema|auth|signature/i.test(err);
|
||||
return /payload_too_large|forbidden|not_found|invalid|schema|auth|signature|member_target_requires_session/i.test(err);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ const SEND_ACK_TIMEOUT_MS = 15_000;
|
||||
* daemon-WS classifier so behavior is identical regardless of which
|
||||
* socket the row went out on. */
|
||||
function classifyPermanent(error: string): boolean {
|
||||
return /unknown|invalid|forbidden|not_authorized|target_not_found/i.test(error);
|
||||
return /unknown|invalid|forbidden|not_authorized|target_not_found|member_target_requires_session/i.test(error);
|
||||
}
|
||||
|
||||
export interface ParentAttestation {
|
||||
|
||||
Reference in New Issue
Block a user