Skip to content

fix(pairing): stop routing mobile pairing to a non-existent /pair sidecar - #5134

Draft
mfethe1 wants to merge 1 commit into
block:mainfrom
mfethe1:fix/pairing-404-no-pair-sidecar
Draft

fix(pairing): stop routing mobile pairing to a non-existent /pair sidecar#5134
mfethe1 wants to merge 1 commit into
block:mainfrom
mfethe1:fix/pairing-404-no-pair-sidecar

Conversation

@mfethe1

@mfethe1 mfethe1 commented Aug 7, 2026

Copy link
Copy Markdown

The bug

Pairing a mobile device against a membership-enforcing relay fails with:

WebSocket connection failed: HTTP error: 404 Not Found

Root cause

probe_pairing_relay treated a NIP-11 supported_nips: [43] advertisement as "this relay has a /pair sidecar" and synthesised wss://host/…/pair.

Nothing serves that path. buzz-relay exposes its WebSocket only at / (crates/buzz-relay/src/router.rs), and the real dedicated pairing relay (buzz-pair-relay) is a separate service, advertised via NIP-11 pairing_relay_url. The /pair convention is dead infrastructure.

NIP-43 was also the wrong signal: it means membership enforcement, not "a pairing sidecar exists". crates/buzz-relay/src/nip11.rs already carries a regression test warning about exactly this ("advertising it on open relays misroutes pairing peers to a non-existent /pair sidecar").

Worse, the fallback could never have worked. On a membership-enforcing relay an unpaired peer is rejected during NIP-42 AUTH (crates/buzz-relay/src/handlers/auth.rsenforce_relay_membership in crates/buzz-relay/src/api/mod.rs), with no exemption for pairing kind:24134 or for ephemeral events. Such a relay genuinely requires a separate open pairing relay — it cannot pair over its own root socket at all.

The change

Replace PairingRelay::LegacyPath with PairingRelay::MembershipWithoutPairingRelay, which returns an actionable error naming the operator fix instead of fabricating a URL that 404s.

The two paths that actually work are unchanged:

Relay Behaviour
Advertises pairing_relay_url Use it as-is
Open relay (no NIP-43) Pair over the relay's root socket
NIP-43, no pairing_relay_url Clear error (was: fabricated /pair → 404)

Scope / operator note

This is a client-side correctness fix. It converts a mystifying 404 into a message that names the cause. Operators of membership-enforcing relays must still deploy a pairing relay for pairing to succeed:

pairingRelay:
  enabled: true
  url: wss://pairing.example.com

(or BUZZ_PAIRING_RELAY_URL directly — see deploy/charts/buzz/README.md).

Testing

cargo test pairing_relay — 7 passed, 0 failed.

Updated the two tests that asserted the /pair behaviour; the replacement asserts the error never contains /pair and does name the missing pairing relay, so the fabrication can't regress.

Open question for maintainers

Should the relay instead exempt pairing kind:24134 from the membership gate, so one relay can serve pairing without a sidecar? That would remove the need for a separate deployment entirely, but it's a change to the auth gate and wants a security review — deliberately not attempted here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TqBMPniDMJfXpwi4XicdMg

…ecar

Pairing from a membership-enforcing relay failed with

    WebSocket connection failed: HTTP error: 404 Not Found

`probe_pairing_relay` treated a NIP-11 `supported_nips: [43]` advertisement
as "this relay has a /pair sidecar" and synthesised `wss://host/…/pair`.
No relay serves that path — `buzz-relay` exposes its WebSocket only at `/`
(crates/buzz-relay/src/router.rs), and the real dedicated pairing relay
(`buzz-pair-relay`) is a separate service advertised via NIP-11
`pairing_relay_url`. The `/pair` convention is dead infrastructure, and
NIP-43 was the wrong signal for it: NIP-43 means membership enforcement,
not the presence of a pairing sidecar.

Worse, the fallback could never have worked. On a membership-enforcing
relay an unpaired peer is rejected during NIP-42 AUTH
(crates/buzz-relay/src/handlers/auth.rs, via `enforce_relay_membership` in
crates/buzz-relay/src/api/mod.rs) and there is no exemption for pairing
kind:24134 or for ephemeral events. Such a relay genuinely requires a
separate open pairing relay; it cannot pair over its own root socket.

Replace `LegacyPath` with `MembershipWithoutPairingRelay`, which surfaces
an actionable error naming the operator fix instead of fabricating a URL
that 404s. The two paths that actually work are unchanged: an advertised
`pairing_relay_url` is used as-is, and an open relay pairs over its root
socket.

This is a client-side correctness fix. Operators of membership-enforcing
relays must still deploy a pairing relay (BUZZ_PAIRING_RELAY_URL, or Helm
`pairingRelay.enabled` + `pairingRelay.url`) for pairing to succeed — the
error now says so instead of reporting a bare 404.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqBMPniDMJfXpwi4XicdMg
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant