fix(pairing): stop routing mobile pairing to a non-existent /pair sidecar - #5134
Closed
mfethe1 wants to merge 1 commit into
Closed
fix(pairing): stop routing mobile pairing to a non-existent /pair sidecar#5134mfethe1 wants to merge 1 commit into
mfethe1 wants to merge 1 commit into
Conversation
mfethe1
force-pushed
the
fix/pairing-404-no-pair-sidecar
branch
from
August 17, 2026 11:11
2385198 to
45a7993
Compare
mfethe1
marked this pull request as ready for review
August 17, 2026 11:11
…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.
Signed-off-by: Michael Feth <michael@jira-flow.com>
mfethe1
force-pushed
the
fix/pairing-404-no-pair-sidecar
branch
from
August 18, 2026 03:41
45a7993 to
22ef8cf
Compare
Author
|
Closing in favour of #6183, which fixes the same defect and goes further. Both PRs found the same root cause — #6183 is a strict superset:
Leaving both open costs a reviewer the work of discovering the overlap themselves and then deciding which to take. There is nothing in this PR that #6183 does not already do better, so this one goes. No action needed on #6183 — flagging the relationship here only so the history is legible. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
probe_pairing_relayindesktop/src-tauri/src/commands/pairing.rsfabricated a pairing-relay URL by appending/pairto the main relay's URL whenever NIP-11 advertisedsupported_nips: [43]. This replaces that fallback (PairingRelay::LegacyPath) withPairingRelay::MembershipWithoutPairingRelay, which returns an actionable error naming the operator fix instead of a URL that 404s.Why
Pairing a mobile device against
wss://flint.communities.buzz.xyz/communityfails with:Two separate defects produce that 404.
1. The fabricated URL is path-scoped; the convention is not. The old code took the relay's existing path and appended
/pair(pairing.rs:715-722onmain), so a community relay served under/communityproducedwss://…/community/pair. But/pairis a reverse-proxy convention for the standalonebuzz-pair-relaysidecar, and it is documented as root-only — "Routes only/pairto this sidecar" (crates/buzz-pair-relay/src/lib.rs:11). Nothing routes/community/pair. The test being replaced asserted that wrong URL verbatim (desktop/src-tauri/src/commands/pairing_relay_tests.rs:92onmain).2. NIP-43 is the wrong signal. NIP-43 means the relay enforces membership; it says nothing about whether a pairing sidecar is deployed.
crates/buzz-relay/src/nip11.rs:483already carries a regression test warning about exactly this misinference.The other route is genuinely closed on such a relay, so falling back to
PairingRelay::MainRelayis not an option either:crates/buzz-relay/src/handlers/event.rs:637-652rejects anyEVENTfrom an unauthenticated connection with no exemption for ephemeral kinds, andcrates/buzz-relay/src/handlers/auth.rs:217-237runsenforce_relay_membershipon every NIP-42 AUTH. An unpaired device cannot publishkind:24134over a membership relay's root socket at all.Behaviour change
pairing_relay_urladvertisedpairing_relay_urlwss://host<path>/pair→ 404This is a breaking change for one deployment shape, and I want that on the record. A membership relay served at the root path, behind a proxy following the same-host convention, got a working
wss://host/pairbefore and gets a hard error now. Migration is a single setting —BUZZ_PAIRING_RELAY_URL=wss://host/pair, or HelmpairingRelay.url— but it is a break, and it makes two in-tree docs stale:deploy/charts/buzz/values.yaml:212-213("the legacy same-host/pairconvention") andcrates/buzz-pair-relay/src/lib.rs:11. I left both untouched pending the decision below.Verification
cargo test --manifest-path desktop/src-tauri/Cargo.toml pairing_relay— 7 tests, all indesktop/src-tauri/src/commands/pairing_relay_tests.rs. One of them,live_nip11_probe_discovers_configured_pairing_relay, binds a local socket and is not#[ignore]d.membership_without_pairing_relay_errors_instead_of_guessing_a_pathasserts the error never contains/pairand does name the missing pairing relay, so the fabrication cannot silently return.pairingErrorMessage(desktop/src/features/settings/ui/MobilePairingCard.tsx:37-50) passes any non-timeout message straight through into the error step. That pass-through is the entire value of the change.action_requiredpending maintainer approval, and DCO/Semgrep/zizmor are the only checks that executed. There is no green build to point at.Scope
desktop/src-tauri/src/commands/pairing.rsgoes 793 → 800 lines, well under the 1000-line ratchet (desktop/scripts/check-file-sizes.mjs).pairing_relay_urldiscovery lives only in this one desktop module.git grep -n -i pairing_relay_urlreturns 33 hits, all inbuzz-relayconfig/nip11, the Helm chart, anddesktop/src-tauri/src/commands/pairing{,_relay_tests}.rs. Mobile receives the already-resolved URL inside the QR payload and never probes NIP-11 itself./pairas a client-supported convention are deliberately left alone pending the first open question.Open questions for maintainers
/pairoutright, or keep it for root-path relays? This PR removes it unconditionally. Gating the fallback onurl.path() == "/"would preserve every deployment the convention ever actually worked for, at the cost of leaving one guess in the client. If you prefer the unconditional removal, I'll fold the two doc updates into this PR.kind:24134from the membership gate? That would let a single relay serve pairing with no sidecar and remove the separate deployment entirely — but it changes the auth gate and wants a security review, so it is deliberately not attempted here.