feat(cli,broker): stable session identity — fix ghost peers + lost DMs (1.35.0)
Session identity is now anchored on Claude Code's session UUID instead of a fresh random keypair per launch. The ed25519 session keypair is generated once per (mesh, session UUID) and persisted under ~/.claudemesh/sessions/<mesh>/<uuid>.json, so relaunching or --resume-ing the same session reuses the same sessionPubkey. Why: a DM is sealed (crypto_box) to the recipient's sessionPubkey. With ephemeral per-launch keys, the pubkey rotated on every relaunch, so queued messages became undecryptable AND the old presence lingered as a same-name ghost that won queued-DM claim races. Reconnecting could not recover the peer because it minted yet another key. On --resume the CLI also registered a throwaway random id unrelated to the resumed session, so the broker never recognized the returning peer. CLI (launch.ts): - resolve the stable UUID for all paths: fresh mints + forces via --session-id; --resume V registers V; --continue resolves the most-recent session UUID from ~/.claude/projects/<cwd>. - use loadOrCreateSessionKeypair(mesh, uuid) instead of generateKeypair(). CLI (daemon/run.ts): - onRegister closes any prior SessionBrokerClient holding the same pubkey under a different token (the leaked-WS ghost). Broker (handleSessionHello): - reattach by sessionPubkey regardless of lease state (online or grace), closing the stale socket — enforces one live presence per session pubkey, killing the duplicate and draining queued DMs on return. Trade-off: session secret keys now persist on disk (the member key already does); SPEC.md updated to reflect the stable-identity model. Older CLIs remain compatible (they keep using ephemeral keys). New: keypair-store.ts + 7 unit tests. Full CLI suite: 114/114 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
4
SPEC.md
4
SPEC.md
@@ -28,7 +28,7 @@ A peer is a Claude Code session connected to a mesh. Ephemeral — comes and goe
|
||||
Two-layer identity:
|
||||
|
||||
- **Member identity** — permanent, created by `claudemesh join`. Keypair stored in `~/.claudemesh/config.json`. Proves authorization to connect.
|
||||
- **Session identity** — ephemeral, generated on every `claudemesh launch`. Fresh ed25519 keypair per session. Provides routing and E2E encryption. Two sessions from the same member have distinct session keys — they can message each other.
|
||||
- **Session identity** — anchored on Claude Code's session UUID (the same identity `--resume` is built on). An ed25519 keypair is generated once per `(mesh, session UUID)` and persisted under `~/.claudemesh/sessions/<mesh>/<uuid>.json`, so relaunching or resuming the same session reuses the same `sessionPubkey`. Provides routing and E2E encryption. Two distinct sessions from the same member have distinct session keys — they can message each other. Because a DM is sealed to the recipient's `sessionPubkey`, a stable key is what lets queued messages both route to and decrypt on the returning session; the broker enforces one live presence per session pubkey.
|
||||
|
||||
### Peer attributes
|
||||
|
||||
@@ -39,7 +39,7 @@ Two-layer identity:
|
||||
| groups | `--groups` flag, wizard, or `join_group` | No | Routing labels with optional per-group role |
|
||||
| status | Hook-driven | No | idle / working / dnd |
|
||||
| summary | `set_summary` tool call | No | 1-2 sentence description of current work |
|
||||
| sessionPubkey | Generated on connect | No | Ephemeral ed25519 pubkey for routing + crypto |
|
||||
| sessionPubkey | Persisted per `(mesh, session UUID)` | Yes (per session UUID) | ed25519 pubkey for routing + crypto; stable across relaunch/`--resume` |
|
||||
| memberId | From `claudemesh join` | Yes | Permanent mesh membership identity |
|
||||
|
||||
### Launch
|
||||
|
||||
Reference in New Issue
Block a user