{"record":{"id":"ff5504e5fa73771f","repo":"bytedance/deer-flow","slug":"unknown-channel-provider","errorCode":null,"errorMessage":"Unknown channel provider","messagePattern":"Unknown channel provider","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"backend/app/gateway/routers/channel_connections.py","lineNumber":216,"sourceCode":"        return repo\n\n    sf = get_session_factory()\n    if sf is None:\n        raise HTTPException(status_code=503, detail=\"Channel connection persistence is not available\")\n\n    repo = ChannelConnectionRepository(sf)\n    request.app.state.channel_connection_repo = repo\n    return repo\n\n\ndef _provider_config(config: ChannelConnectionsConfig, provider: str):\n    # Resolve provider configs only for known providers. An unrestricted\n    # getattr would let a request-supplied name that happens to match another\n    # config attribute (e.g. the \"enabled\" / \"require_bound_identity\" bool\n    # fields) slip past the 404 and return a non-provider value, which callers\n    # then dereference as a provider config (AttributeError -> HTTP 500).\n    if provider not in _PROVIDER_META:\n        raise HTTPException(status_code=404, detail=\"Unknown channel provider\")\n    provider_config = getattr(config, provider, None)\n    if provider_config is None:\n        raise HTTPException(status_code=404, detail=\"Unknown channel provider\")\n    return provider_config\n\n\ndef _runtime_channel_configured(provider: str, channels_config: dict[str, Any]) -> bool:\n    runtime_config = channels_config.get(provider)\n    if not isinstance(runtime_config, dict) or not runtime_config.get(\"enabled\", False):\n        return False\n    return all(str(runtime_config.get(key) or \"\").strip() for key in _RUNTIME_REQUIREMENTS[provider])\n\n\ndef _runtime_unavailable_reason(provider: str) -> str:\n    meta = _PROVIDER_META.get(provider)\n    display_name = meta[\"display_name\"] if meta else provider\n    return f\"Enter the required {display_name} credentials to connect this channel.\"\n","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/channel_connections.py#L198-L234","documentation":"404 from _provider_config (channel_connections.py:216): the requested provider name is not a key in _PROVIDER_META (the router's allowlist of known channel providers: telegram, slack, discord, feishu, dingtalk, wechat, wecom, buzz). The allowlist exists deliberately — an unrestricted getattr would let a request-supplied name matching a non-provider config attribute (like 'enabled' or 'require_bound_identity') slip past the 404 and be dereferenced as a provider config, causing a 500.","triggerScenarios":"Calling /api/channels/connections/{provider}/... with a provider not in the fixed set — e.g. 'whatsapp', 'email', 'ENABLED', or any typo like 'slackk'.","commonSituations":"Client written against docs listing providers the backend version doesn't support (provider set differs across DeerFlow versions); automated client iterating a hardcoded provider list that includes unsupported names; probing the endpoint with config-attribute names.","solutions":["Use one of the supported provider slugs exactly: telegram, slack, discord, feishu, dingtalk, wechat, wecom, buzz","Fetch the provider list from the API's discovery/metadata endpoint (or _PROVIDER_META-backed listing) instead of hardcoding, so version drift cannot produce unknown names","Upgrade the backend if you need a provider added in a newer release"],"exampleFix":"# before\ncurl /api/channels/connections/whatsapp/connect -X POST\n# -> 404 Unknown channel provider\n\n# after\ncurl /api/channels/connections/feishu/connect -X POST","handlingStrategy":"type-guard","validationCode":"const SUPPORTED = new Set([\"telegram\",\"slack\",\"discord\",\"feishu\",\"dingtalk\",\"wechat\",\"wecom\",\"buzz\"]);\nif (!SUPPORTED.has(provider)) throw new Error(`Unsupported provider: ${provider}`);","typeGuard":"const CHANNEL_PROVIDERS = [\"telegram\",\"slack\",\"discord\",\"feishu\",\"dingtalk\",\"wechat\",\"wecom\",\"buzz\"] as const;\ntype ChannelProvider = typeof CHANNEL_PROVIDERS[number];\nconst isChannelProvider = (p: string): p is ChannelProvider =>\n  (CHANNEL_PROVIDERS as readonly string[]).includes(p);","tryCatchPattern":"try { await connectProvider(provider) } catch (e) { if (e.status === 404 && e.detail === \"Unknown channel provider\") filterProviderFromUI(provider); else throw e; }","preventionTips":["Drive provider lists from the API's own metadata endpoint instead of hardcoding","Keep the client's provider list in lockstep with the deployed backend version","Never probe with config-attribute-like names — the allowlist 404 is intentional hardening"],"tags":["channels","validation","http-404","provider"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}