Skip to content

Workflows list returns empty for any account with more than one channel #4804

Description

@sdonoghu75

Workflows list returns empty for any account with more than one channel

Summary

The desktop app's Workflows screen (/workflows) always shows "No workflows yet," even for channels that genuinely have workflow definitions saved. Confirmed via buzz-cli that the data is present and correctly stored on the relay — this is a read-side bug, not a data-loss bug.

Repro steps

  1. Be a member of 2+ channels (true for basically every real account).
  2. Create a workflow in any channel via the desktop app's Workflows screen (Create Workflow → fill in trigger/steps → Create). The dialog closes with no error, confirming the relay accepted it.
  3. Go to the Workflows overview screen. It shows "No workflows yet," including after clicking the manual refresh control and after a full app quit/relaunch.
  4. Confirm the data really exists: build buzz-cli and run buzz workflows list --channel <that-channel-id> authenticated as the same user (or any member). The workflow is returned, full definition intact.

Root cause

WorkflowsView.tsx batches its list query across every channel the user belongs to in a single relay filter, rather than querying per-channel (desktop/src/features/workflows/ui/WorkflowsView.tsx, getChannelsWorkflows(channelIds) → one {"kinds": [30620], "#h": [id1, id2, ...]} filter with N channel ids).

On the relay side, both the desktop app and buzz-cli land in the same handler — POST /query (crates/buzz-relay/src/api/bridge.rs, query_events / query_events_authed). The catch-all filter path there calls extract_channel_from_filter to resolve which channel a filter is scoped to:

// crates/buzz-relay/src/api/bridge.rs
fn extract_channel_from_filter(filter: &nostr::Filter) -> Option<uuid::Uuid> {
    let h_tag = nostr::SingleLetterTag::lowercase(nostr::Alphabet::H);
    filter.generic_tags.get(&h_tag).and_then(|vs| {
        if vs.len() == 1 {
            vs.iter().next()?.parse::<uuid::Uuid>().ok()
        } else {
            None
        }
    })
}

This only resolves a scope when the #h tag list has exactly one value. With 2+ values (the batched multi-channel case the Workflows screen relies on), it returns None on every call. Whatever happens downstream of that None (broadened access-scope query, d_tag/NIP-33 lookup skipped, or something in build_event_query_from_filter / the DB layer) ends up excluding kind:30620 results specifically — I didn't trace the exact final SQL step, but the failure is 100% reproducible and starts exactly here: single-value #h filters work (proven via buzz-cli), multi-value #h filters against kind 30620 don't (proven via the desktop app, which is the only caller using multi-value #h for this kind).

The comment in WorkflowsView.tsx confirms this batching was a deliberate, relatively recent optimization ("replaces the per-channel fanout" — previously one get_channel_workflows call per member channel via Promise.all), which is presumably why this wasn't caught earlier — the old per-channel-call pattern would have sent single-value #h filters and never hit this branch.

Suggested fix

Either:

  • extract_channel_from_filter (and whatever consumes its None result for kind:30620 specifically) should treat multiple #h values as "any of these," matching standard NIP-01 tag-filter semantics, instead of "ambiguous → no scope"; or
  • The desktop's batched query should stay batched for scope purposes but not depend on this single-channel-only resolution path for kind:30620.

Secondary, smaller finding along the way

buzz workflows delete --workflow <id> (crates/buzz-relay/src/handlers/side_effects.rs, handle_a_tag_deletion) correctly removes the workflow from the operational workflows table and invalidates the engine's cache — so a deleted workflow genuinely stops being live/executable. But it doesn't touch the underlying raw kind:30620 event, so buzz workflows list/get (and presumably the same desktop code path, once the bug above is fixed) will keep showing the "deleted" workflow as a stale historical event indefinitely. Might be intentional (append-only audit trail), but it's surprising for a delete command to leave the object still listed — worth at least a doc note if it's by design.

Environment

  • buzz repo, main branch, commit 35305bf.
  • Reproduced against a self-hosted relay (Docker Compose), not buzz.dev's hosted relay — but the bug is in shared relay code, not deployment-specific.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions