The sender exclusion filter (excludeSenderSessionPubkey) was blocking
delivery of ALL messages from the sender, including direct messages
to other peers. Now only excludes on broadcast (target_spec = '*').
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Node.js stdout to a pipe is buffered. Without periodic event loop
activity, WS callback → server.notification() → stdout.write() may
not flush until the next I/O event. A 1s setInterval (NOT unref'd)
keeps the event loop ticking so notifications flush immediately.
This is why claude-intercom worked: its 1s HTTP poll kept the event
loop active as a side effect. Claudemesh's passive WS listener let
the event loop settle, causing stdout to buffer indefinitely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sends test messages to self through the full pipeline per priority
and measures round-trip timing. Reports send→ack and send→receive
latency. Detects broker priority gating (status=working holds next/low).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert poll-based drain (v0.5.2 overcorrection). Claude Code source
confirms notifications are processed event-driven via React
useEffect, not polled. The WS onPush → server.notification() path
is correct.
Added section 13 to SPEC.md documenting the full Claude Code
notification pipeline, feature gates, priority gating, and common
push delivery issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace WS onPush→notification with timer-based buffer drain.
The old claude-intercom used 1s polling and worked reliably.
WS async callbacks may not flush stdio properly for MCP
notifications. Polling on a timer ensures consistent delivery.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
--inbox: count-only notifications, no content in context
--no-messages: tools only, zero prompt injection risk
Default: push (real-time, current behavior)
Wizard shows mode picker when no flag provided.
MCP instructions tell Claude its current mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hero: sessions form a team with groups, state, memory — not just
messaging. Features: 4 tabs with real CLI code (groups, state,
memory, coordination patterns). Use cases: team sprint with 5
agents, new-hire knowledge transfer via recall(), deploy-frozen
via shared state. All match the shipped spec (v0.3.0).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files: MinIO-backed file sharing built into the broker.
share_file for persistent mesh files, send_message(file:) for
ephemeral attachments. Presigned URLs for download, access
tracking per peer.
Broker infra: MinIO in docker-compose, internal network.
HTTP POST /upload endpoint. WS handlers for get_file,
list_files, file_status, delete_file.
Multi-target: send_message(to:) accepts string or array.
Targets deduplicated before delivery.
Targeted views: MCP instructions teach Claude to send
tailored messages per audience instead of generic broadcasts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase B + C + message delivery status.
State: shared key-value store per mesh. set_state pushes changes
to all peers. get_state/list_state for reads. Peers coordinate
through shared facts instead of messages.
Memory: persistent knowledge with full-text search (tsvector).
remember/recall/forget. New peers recall context from past sessions.
message_status: check delivery status with per-recipient detail
(delivered/held/disconnected).
Multicast fix: broadcast and @group messages now push directly to
all connected peers instead of racing through queue drain.
MCP instructions: dynamic identity injection (name, groups, role),
comprehensive tool reference, group coordination guide.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase A of the claudemesh spec. Peers can now join named groups
with roles, and messages route to @group targets.
Broker:
- @group routing in fan-out (matches peer group membership)
- @all alias for broadcast
- join_group/leave_group WS messages + DB persistence
- list_peers returns group metadata
- drainForMember matches @group targetSpecs in SQL
CLI:
- join_group/leave_group MCP tools
- send_message supports @group targets
- list_peers shows group membership
- PeerInfo includes groups array
- Peer name cache for push notifications
Launch:
- --role flag (optional peer role)
- --groups flag (comma-separated, e.g. "frontend:lead,reviewers")
- Interactive wizard for role + groups when flags omitted
- Groups written to session config for broker hello
Spec: SPEC.md added with full v0.2 vision (groups, state, memory)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
excludeSenderMemberId blocked delivery to ALL peers sharing the
same member_id (all sessions from one join). Replaced with
excludeSenderSessionPubkey which only excludes the sender's own
session — peers with different session pubkeys receive correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Write displayName into tmpdir config.json so the MCP server reads
it directly. Env vars from claudemesh launch may not propagate to
MCP child processes spawned by Claude Code. Config file is reliable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
claudemesh launch now passes --dangerously-skip-permissions to
claude so peers can chat without per-tool-call approval prompts.
Shows a clear explanation before launch; user confirms with Enter.
Skip with -y/--yes for CI or repeat launches.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
onPush now queries list_peers to resolve the sender's pubkey to their
display name. Instructions updated to tell Claude to reply by name
instead of raw pubkey. Fixes two-way messaging between named peers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store sender's sessionPubkey on message_queue at send time.
drainForMember returns COALESCE(sender_session_pubkey, peer_pubkey)
so the recipient gets the correct sender key for decryption.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each WS connection generates its own ed25519 keypair (sessionPubkey)
sent in the hello handshake. The broker stores it on the presence
row and uses it for message routing + list_peers. This gives every
`claudemesh launch` a unique crypto identity without burning invite
uses — member auth stays permanent, session identity is ephemeral.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Zod schemas with plain TypeScript validation in env.ts,
config.ts, and invite/parse.ts. Zod 4 classes break under bun
build --target=node (Class2 is not a constructor).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
resolveClient() now resolves display names via list_peers WS query.
Supports exact match, partial match (unique substring), and falls
back to pubkey/channel/broadcast pass-through.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The turbopack.rules config for @svgr/webpack was removed during
the Payload integration attempts. Without it, SVG imports return
raw module objects instead of React components. This crashes
LocaleCustomizer → Icons.UnitedKingdom → object → React #130.
Next.js 16.2.2 supports turbopack in production builds, so this
config is safe now.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The hydration crash exists on both 16.0.10 and 16.2.2 — it's a
pre-existing component bug, not a Next.js regression. Stay on
latest for security + Payload compat when we re-add it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire list_peers and set_summary MCP tools to the broker's WS
protocol instead of returning stubs. Peers can now discover each
other, see status/summary, and route messages by display name.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Next.js 16.2.2 causes React #130 on client hydration in
production standalone output. Server renders fine but client
JS crashes. Downgrade to 16.0.10 which was the last working
version. Payload CMS is fully removed from prod so the
turbopack restriction is no longer relevant.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove ALL Payload imports, withPayload wrapper, and (payload)
routes. Blog index + changelog are now static data arrays.
Blog post at /blog/peer-messaging-claude-code is static TSX.
Payload CMS stays as a dev dependency for future local admin
but has zero presence in the production build.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Final working pattern: withPayload via require() for build
compatibility, admin page replaced with redirect (no RootPage
import = no React #130), payload packages externalized from
turbopack bundle. Blog/changelog use server-side getPayload().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Turbopack tries to parse esbuild's native binary as JS, causing
build failure. Externalize all Payload-related packages so they
resolve at runtime, not bundled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
withPayload crashes ALL routes with React #130 in standalone
output — even with admin page replaced by redirect. The wrapper
injects a client-side ConfigProvider that fails hydration.
Removed: withPayload wrapper, entire (payload) route group.
Kept: payload.config.ts, migrations, blog/changelog server-side
queries with graceful DB fallback.
Payload admin runs on local dev only (add withPayload back in
next.config when running pnpm dev). Production content via
static TSX pages or future API-based publishing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep withPayload (needed for build compilation) but replace the
admin RootPage with a redirect. The RootPage's ConfigProvider
causes React #130 in standalone output. Blog/changelog use
server-side getPayload() which works fine.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The withPayload wrapper injects a client-side ConfigProvider that
crashes hydration on every route when the Payload admin can't
initialize in standalone output. Blog/changelog pages use server-
side getPayload() which works without the wrapper.
Payload admin at /payload is disabled until standalone server
init is implemented. All user-facing content works.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replicate working cuidecar Payload setup:
- require() instead of ESM import for withPayload
- routes.admin = "/payload" to avoid /admin conflicts
- (payload)/payload/ route group with own layout + importMap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Payload CMS integration crashes the entire production app — the
withPayload wrapper + admin routes break when DB tables don't
exist and the layout conflicts with i18n routing.
Keeping: payload.config.ts, blog/changelog pages with graceful
DB fallback, static blog post page. Payload admin will be added
back once properly integrated with a dedicated route group that
doesn't inherit the main app layout.
The blog post at /blog/peer-messaging-claude-code is static TSX
and works without Payload runtime.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Static TSX page at /blog/peer-messaging-claude-code while Payload
admin is not yet configured in production. Full 1100-word post on
protocol, dev-channels, prompt-injection, and next steps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>