{"record":{"id":"c7801c74cb15429d","repo":"tinyhumansai/openhuman","slug":"allowlist-persist-unsupported-for-channel-other","errorCode":null,"errorMessage":"allowlist persist unsupported for channel '{other}'","messagePattern":"allowlist persist unsupported for channel '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/channels/host/adapters.rs","lineNumber":313,"sourceCode":"        let mut config: Config =\n            toml::from_str(&contents).context(\"failed to parse config.toml for allowlist\")?;\n        config.config_path = config_path;\n        config.workspace_dir = openhuman_dir.join(\"workspace\");\n\n        match channel {\n            \"telegram\" => {\n                let Some(telegram) = config.channels_config.telegram.as_mut() else {\n                    anyhow::bail!(\"telegram channel config is missing in config.toml\");\n                };\n                if !telegram.allowed_users.iter().any(|u| u == &normalized) {\n                    telegram.allowed_users.push(normalized);\n                    config\n                        .save()\n                        .await\n                        .context(\"failed to persist allowlist to config.toml\")?;\n                }\n            }\n            other => anyhow::bail!(\"allowlist persist unsupported for channel '{other}'\"),\n        }\n        tracing::debug!(\"{LOG_PREFIX} persisted allowed identity for channel={channel}\");\n        Ok(())\n    }\n}\n\n// ---------------------------------------------------------------------------\n// EventSink → routes provider events to the right OpenHuman bus\n// ---------------------------------------------------------------------------\n\n/// Routes provider events by `domain`:\n/// - `\"web\"`     → the web channel's `WebChannelEvent` broadcast bus (payload\n///   must deserialize into a `WebChannelEvent`; presentation builds that shape).\n/// - `\"channel\"` → the global `DomainEvent` bus (telegram reaction fan-out).\n///\n/// One capability, two backends — providers don't know which bus they hit.\npub struct OpenHumanEventSink;\n","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/channels/host/adapters.rs#L295-L331","documentation":"`persist_allowed_identity` implements exactly one channel arm — `\"telegram\"`. Every other channel name falls into the `other` wildcard arm and bails with `allowlist persist unsupported for channel '{other}'`. This is a deliberate capability gap: no provider other than Telegram has on-disk allowlist persistence to config.toml via this store yet.","triggerScenarios":"A channel adapter for any non-telegram provider (discord, whatsapp, slack, signal, …) is wired to `ConfigAllowlistStore` and calls `persist_allowed_identity` with its channel name; also a misspelled or differently-cased channel key (e.g. \"Telegram\", \"tg\") that no longer matches the one implemented arm.","commonSituations":"Adding a new channel provider to the host and reusing the shared allowlist store; refactors that rename channel identifier strings; integrations that assume feature parity across channels.","solutions":["Add a match arm for the channel mirroring the telegram arm (mutate that provider's config field, e.g. `config.channels_config.<provider>.allowed_users`, and save).","If the channel does not need on-disk allowlist persistence, gate the persist call at the call site so only telegram reaches the store.","Check the channel string matches the arm exactly — lowercase `\"telegram\"`."],"exampleFix":"// before\nmatch channel {\n    \"telegram\" => { /* ... */ }\n    other => anyhow::bail!(\"allowlist persist unsupported for channel '{other}'\"),\n}\n\n// after — implement the new provider's arm\nmatch channel {\n    \"telegram\" => { /* ... */ }\n    \"discord\" => {\n        let Some(discord) = config.channels_config.discord.as_mut() else {\n            anyhow::bail!(\"discord channel config is missing in config.toml\");\n        };\n        if !discord.allowed_users.iter().any(|u| u == &normalized) {\n            discord.allowed_users.push(normalized);\n            config.save().await.context(\"failed to persist allowlist to config.toml\")?;\n        }\n    }\n    other => anyhow::bail!(\"allowlist persist unsupported for channel '{other}'\"),\n}","handlingStrategy":"type-guard","validationCode":"if supports_config_allowlist_persist(channel) {\n    store.persist_allowed_identity(channel, identity).await?;\n} // else: this channel has no on-disk allowlist persistence — skip silently","typeGuard":"fn supports_config_allowlist_persist(channel: &str) -> bool {\n    matches!(channel, \"telegram\")\n}","tryCatchPattern":"match store.persist_allowed_identity(channel, identity).await {\n    Err(e) if e.to_string().starts_with(\"allowlist persist unsupported\") => {\n        // capability gap, not a failure — skip persistence for this channel\n    }\n    other => other?,\n}","preventionTips":["Check which channel arms ConfigAllowlistStore implements before wiring a new provider to it","Keep channel identifier strings lowercase and exact so they match the implemented arms","When adding a provider, add its match arm and config section handling in the same change"],"tags":["channels","allowlist","unsupported-operation","provider-integration"],"backgroundTag":"unsupported-operation","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}