zeroclaw-labs/zeroclaw · warning · DiagItem
channels.discord.{alias}.bot_token is unset but the channel
Error message
channels.discord.{alias}.bot_token is unset but the channel is enabled — the channel cannot connect until a bot token is set What it means
A `zeroclaw doctor` warning from `check_config_semantics`: the Discord twin of the Telegram token check — a Discord alias has `enabled = true` while its `bot_token` is an unset display value. The serde default on `bot_token` means the partial alias loads without entering `degraded_sections`, so doctor names the unset field before the channel fails to connect.
Source
Thrown at crates/zeroclaw-runtime/src/doctor/mod.rs:1349
}
// Enabled bot channels with no token: a partial alias survives the
// resilient load (`bot_token` has a serde default), so it never
// reaches `degraded_sections` — doctor must name the unset field here
// or the operator only finds out when the channel fails to start.
for (alias, tg) in &cc.telegram {
if tg.enabled && zeroclaw_config::traits::is_unset_display_value(&tg.bot_token) {
items.push(DiagItem::warn(
cat,
format!(
"channels.telegram.{alias}.bot_token is unset but the channel is enabled — the channel cannot connect until a bot token is set"
),
));
}
}
for (alias, dc) in &cc.discord {
if dc.enabled && zeroclaw_config::traits::is_unset_display_value(&dc.bot_token) {
items.push(DiagItem::warn(
cat,
format!(
"channels.discord.{alias}.bot_token is unset but the channel is enabled — the channel cannot connect until a bot token is set"
),
));
}
}
// Delegate agents: model_provider validity (resolved from model_provider alias)
let mut agent_names: Vec<_> = config.agents.keys().collect();
agent_names.sort();
for name in agent_names {
let agent = config.agents.get(name).unwrap();
let provider_ref = agent.model_provider.as_str();
if provider_ref.is_empty() {
continue;
}
if let Some(reason) = provider_validation_error(config, provider_ref) {View on GitHub (pinned to 88bb9c8533)
Solutions
- Set a real token from the Discord developer portal in `channels.discord.<alias>.bot_token`, or export the env var it references where the daemon runs.
- For systemd, wire `EnvironmentFile=` into the unit so the placeholder resolves at start.
- Set `enabled = false` if the channel should not connect yet.
- Re-run `zeroclaw doctor` to confirm the warning cleared.
Example fix
# before [channels.discord.default] enabled = true bot_token = "" # after [channels.discord.default] enabled = true bot_token = "MTIzNDU2Nzg-your-discord-token"
Defensive patterns
Strategy: validation
Validate before calling
for (alias, dc) in &config.channels.discord {
if dc.enabled && zeroclaw_config::traits::is_unset_display_value(&dc.bot_token) {
eprintln!(
"channels.discord.{alias}.bot_token unset — set the token or disable the channel"
);
}
} Type guard
fn channel_ready(enabled: bool, bot_token: &str) -> bool {
!enabled || !zeroclaw_config::traits::is_unset_display_value(bot_token)
} Prevention
- Source the Discord token from the same secret file pattern as Telegram so one env file covers all channels.
- Rotate tokens by editing the env file and restarting — never leave placeholder values with enabled = true.
When it happens
Trigger: Running `zeroclaw doctor` when `channels.discord.<alias>` sets `enabled = true` but `bot_token` is empty or an unresolved `${...}` placeholder (`zeroclaw_config::traits::is_unset_display_value`).
Common situations: Discord bot token from the developer portal never pasted into config; env-var-indirected token missing from the systemd unit or container env; enabling the Discord alias before finishing setup.
Related errors
- channels.telegram.{alias}.bot_token is unset but the channel
- no channels configured — run `zeroclaw quickstart` to set on
- model route "{}" uses invalid model_provider "{}": {}
- embedding route "{}" uses invalid model_provider "{}": {}
- agent "{name}" uses invalid model_provider "{provider_ref}":
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/730f1d3bf94c68d7.
Report an issue: GitHub.