bytedance/deer-flow · error · ValueError
channels.buzz.relay_url must be a ws:// or wss:// URL
Error message
channels.buzz.relay_url must be a ws:// or wss:// URL
What it means
BuzzChannel's constructor validates channels.buzz.relay_url at channel construction time: after stripping whitespace it must start with ws:// or wss://. This is fail-fast configuration validation — the relay URL is also used to derive _workspace_id via urlparse().netloc, so a malformed URL would corrupt inbound dedupe and the persisted connection row downstream.
Source
Thrown at backend/app/channels/buzz.py:258
transient and retried. The connector's worst failure mode is going silently
deaf, so an unknown reason resolves toward "keep listening";
``MAX_RESUBSCRIBE_ATTEMPTS`` is what stops that from becoming a loop when the
guess is wrong.
"""
text = (reason or "").strip().lower()
if text.startswith(_PERMANENT_CLOSE_PREFIXES):
return False
return not any(marker in text for marker in _PERMANENT_CLOSE_MARKERS)
class BuzzChannel(Channel):
_connect: Any = None # test seam: async callable returning an async-context-manager transport
def __init__(self, bus: MessageBus, config: dict[str, Any]) -> None:
super().__init__(name="buzz", bus=bus, config=config)
self._relay_url = str(config.get("relay_url", "")).strip()
if not self._relay_url.startswith(("ws://", "wss://")):
raise ValueError("channels.buzz.relay_url must be a ws:// or wss:// URL")
# One community per relay URL (see the design's multi-community note), so the
# relay host is this channel's workspace: it scopes inbound dedupe, the
# persisted connection row written by `/connect`, and the lookup that resolves
# that row back on the inbound path. Computed once here so those three uses
# can never drift apart.
self._workspace_id = urlparse(self._relay_url).netloc
self._private_key_raw = str(config.get("private_key", ""))
self._keys: buzz_nostr.NostrKeys | None = None # parsed in start() so coincurve stays lazy
self._allowed_users = {buzz_nostr.parse_pubkey(v) for v in config.get("allowed_users", []) or []}
self._require_mention = bool(config.get("require_mention", True))
self._mention_free = {str(c) for c in config.get("mention_free_channels", []) or []}
self._channel_meta: dict[str, dict[str, Any]] = {}
self._stream_targets: dict[tuple[str, str | None], str] = {}
self._stream_tails: dict[tuple[str, str | None], list[str]] = {} # overflow chunk ids beyond chunk 0, per conversation
self._last_requester: dict[tuple[str, str | None], str] = {}
self._pending_auth_challenge: str | None = None # set from an AUTH relay frame; consumed by the NIP-42 flow in _session
self._seen_created_at: dict[str, int] = {} # channel id -> high-water mark of PROCESSED created_at (see _advance_watermark)
self._chat_subscriptions: set[str] = set() # channel ids with a live per-channel REQ on the CURRENT connectionView on GitHub (pinned to 1dd6ba1acb)
Solutions
- Set channels.buzz.relay_url to the relay's websocket endpoint, e.g. wss://relay.example.com.
- Confirm the relay actually speaks websocket (most Nostr relays publish both forms — use the ws one).
- If you do not intend to run Buzz, remove or disable the channels.buzz block instead of leaving a placeholder URL.
- Restart the Gateway after fixing config.yaml.
Example fix
# before
channels:
buzz:
enabled: true
relay_url: https://relay.example.com
# after
channels:
buzz:
enabled: true
relay_url: wss://relay.example.com Defensive patterns
Strategy: validation
Validate before calling
from urllib.parse import urlparse
RELAY = config.get('channels', {}).get('buzz', {}).get('relay_url', '')
if RELAY and urlparse(RELAY).scheme not in ('ws', 'wss'):
raise SystemExit(f"invalid buzz relay_url: {RELAY!r} — must be ws:// or wss://") Try / catch
try:
channel = BuzzChannel(bus, cfg)
except ValueError as e:
logger.error('buzz channel disabled: %s', e)
# skip registration instead of crashing the channel manager Prevention
- Validate channels.buzz.relay_url in `make doctor`/config schema so the error surfaces before startup.
- Copy relay URLs from the relay's websocket documentation, not its landing page.
- Unit-test channel constructors against the example config in CI.
When it happens
Trigger: config.yaml contains channels.buzz.relay_url set to an http(s):// URL, a bare hostname like relay.example.com, an empty string, or a value with leading spaces that strips to none of the accepted schemes. The channel manager instantiates BuzzChannel during startup or config reload, and the ValueError aborts registration.
Common situations: Operator copies a nostr relay's https:// web URL instead of its websocket endpoint; forgets the scheme entirely; leaves relay_url empty while enabling the buzz channel; typo like wss:/ (single slash).
Related errors
- expected 64-hex or {bech_hrp}1... value
- expected exactly 32 bytes
- Failed to update MCP configuration
- not bech32: {value!r}
- expected {expected_hrp!r} bech32, got {hrp!r}
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/90079febc0c42ad0.
Report an issue: GitHub.