Skip to content

fix(relay): stop collapsing multi-value #h filters to a single channel - #2452

Open
taehalim wants to merge 1 commit into
block:mainfrom
taehalim:fix/multi-h-filter-collapse
Open

fix(relay): stop collapsing multi-value #h filters to a single channel#2452
taehalim wants to merge 1 commit into
block:mainfrom
taehalim:fix/multi-h-filter-collapse

Conversation

@taehalim

Copy link
Copy Markdown

Problem

Fixes #2385.

A filter listing multiple distinct #h channels should match events from any of them (NIP-01 OR semantics), but extract_channel_id_from_filter in handlers/req.rs returned the first parseable value from the tag BTreeSet — i.e. the lexicographically-first channel UUID. Through build_event_query_from_filter this pinned query.channel_id to that single channel, so:

  • POST /query (bridge catchall) composed a contradictory SQL query (channel_id = $first AND-ed with the access scope) and dropped every other listed channel's events — this is why the desktop Workflows overview renders empty: get_channels_workflows batches all member channels into one #h filter.
  • The COUNT fallback paths (WS COUNT and POST /count) undercounted for the same reason.

Fix

extract_channel_id_from_filter now returns None when the filter carries multiple distinct parseable #h UUIDs, mirroring the existing multi-filter rule in extract_channel_id_from_filters. Callers then take the access-scope path (apply_access_scope_to_query widens the SQL query to the caller's accessible channels) and the filters_match post-filter enforces the per-event #h OR — the same shape the WS REQ path already uses.

A filter whose #h values resolve to a single channel (duplicates, or unparseable extras alongside one UUID) still pins that channel, keeping the narrower SQL predicate.

Testing

  • Unit tests for the extractor: single value, multiple distinct values, duplicate spellings of the same UUID, unparseable values.
  • New e2e regression test test_query_multi_h_filter_returns_all_listed_channels: two channels, one message each; POST /query with both ids in one #h filter must return both events, and POST /count must count both. Verified it fails on main with exactly the reported symptom (only the lexicographically-first channel's event comes back) and passes with this fix.
  • cargo fmt + clippy --all-targets --all-features clean on the touched crates; full e2e_relay suite run against a local relay — 36/39 pass, and the 3 failures (invite mint, subscription limit, unarchive notification) fail identically on main, so they're local-env issues unrelated to this change.

extract_channel_id_from_filter returned the first parseable #h value even
when a filter listed several distinct channels. Filter tag values live in
a BTreeSet, so /query and the COUNT fallback silently pinned the SQL query
to the lexicographically-first channel UUID and dropped every other listed
channel's events — the desktop Workflows overview rendered empty because
get_channels_workflows batches all member channels into one #h filter.

Return None for multi-#h filters instead, mirroring the multi-filter rule
in extract_channel_id_from_filters: the access-scope path then widens the
SQL query to the caller's accessible channels and the filters_match
post-filter enforces the per-event #h OR.

Fixes block#2385

Signed-off-by: taeha <uv.taeha@gmail.com>
@chillerno1

Copy link
Copy Markdown
Contributor

Independently hit this on a hosted community relay: Workflows overview shows "No workflows yet" despite a live kind:30620 in a joined channel. Reproduced the collapse with signed /query calls — #h:[A] returns the event, #h:[B,A] returns [] when B sorts first and holds none. The patch here matches the root cause we traced to extract_channel_id_from_filter. Would be great to see this land.

@taehalim

Copy link
Copy Markdown
Author

Bumping with fresh signal: three independent repros now — #2373 (Desktop Workflows overview empty) plus two hosted-relay confirmations in this thread and #2385, all tracing to the same extract_channel_id_from_filter collapse. CI is green and the branch is conflict-free. Happy to rebase or adjust if a reviewer has a preferred shape.

@diripper

diripper commented Aug 3, 2026

Copy link
Copy Markdown

Another self-hosted operator blocked by this — confirming on Desktop 0.5.3 against a self-hosted relay, and adding one detail I did not find in #2385 or #4419:

There is no UI workaround. Both code paths that would be unaffected by the multi-#h collapse exist, but neither is reachable:

  • useChannelWorkflowsQuery (single-channel, so not subject to the collapse) is defined in desktop/src/features/workflows/hooks.ts and is not called by any component.
  • WorkflowDetailPanel loads through useWorkflowQuery(workflowId) — a by-id lookup, also unaffected — but /workflows/$workflowId is only reachable by clicking a row in the (empty) overview. There is no buzz://workflow deep link; the handler covers only message, join and connect.

So the impact goes beyond visibility: an owner cannot reach their workflows through the UI at all. In our case that produced four duplicate scheduled workflows — each created because the previous one never appeared — which then had to be removed with buzz workflows delete and verified directly in the relay's workflows table, since buzz workflows list keeps reporting deleted workflows (#2879).

Single-channel CLI reads return everything correctly, consistent with the analysis in #2385.

Happy to test a build against a self-hosted relay if that helps.

@jdrolls

jdrolls commented Aug 4, 2026

Copy link
Copy Markdown

Production verification from a self-hosted relay — this patch fixes the reported symptom end to end.

Setup. Built the relay image from 54c8ef30 (the exact org.opencontainers.image.revision of the ghcr.io/block/buzz:main image the deployment was already running) with this PR applied on top, so the only delta between the before and after state is this change. Client was Buzz Desktop v0.5.4.

Before. Several member channels, with the channel owning the workflow not sorting first. The Workflows overview showed No workflows yet indefinitely — refresh, app restart, and relay restart all had no effect, exactly as described in #2385. A single-channel read returned the workflow immediately, confirming the events were present and readable the whole time.

After. No client change, no cache clear, no re-login — the Workflows overview lists the workflow on first load, with its schedule and description rendered correctly.

One observation that may be useful for reviewers: the scheduler was never affected. The workflow kept firing on its cron throughout, since that path reads the database directly and never goes through the bridge. The blast radius really is visibility-only — but a user watching an empty overview cannot distinguish a broken list from a workflow that failed to save, which is how duplicate workflows get created (as @ngavelek reported above).

Scope of what I tested: the /query path behind the Desktop Workflows overview, on kind:30620 with a multi-value #h. I did not independently exercise the /count path that this PR's e2e test also covers.

Happy to run any additional check against the patched deployment if that would help land this.

No relay hostname, community or channel names, UUIDs, public keys, IP addresses, or workflow content are included in this report.

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.

relay: multi-value #h filters collapse to lexicographically-first channel (Workflows overview shows empty)

4 participants