zeroclaw-labs/zeroclaw · error · anyhow::Error
approval route '{route}' is not 'channel:recipient' (e.g. 'd
Error message
approval route '{route}' is not 'channel:recipient' (e.g. 'discord.ops:123456789') - both halves must be non-empty What it means
Approval notices are routed by a config string 'channel:recipient'. build_delivery() parses it and requires both halves non-empty (e.g. 'discord.ops:123456789'); a route without a colon, or with an empty channel or recipient half, fails before any message is built.
Source
Thrown at crates/zeroclaw-runtime/src/sop/approval/channel_route.rs:356
}
},
description,
reference: gate_reference(notice),
choices,
resolved_description: Some(resolved_description),
}
}
/// Build the (channel_key, message) delivery pair from a route + run identity, or an
/// error describing why it can't be built. PURE (no I/O, no spawn) so the parse +
/// message-shaping is unit-testable without a runtime.
fn build_delivery(
kind: ApprovalNoticeKind,
route: &str,
notice: &GateNotice<'_>,
) -> anyhow::Result<(String, SendMessage)> {
let Some((channel_key, recipient)) = parse_approval_route(route) else {
anyhow::bail!(
"approval route '{route}' is not 'channel:recipient' (e.g. \
'discord.ops:123456789') - both halves must be non-empty"
);
};
let msg = SendMessage::new(render_notice(kind, notice), recipient).suppress_voice();
Ok((channel_key.to_string(), msg))
}
impl ApprovalRouteAdapter for ChannelRouteAdapter {
fn deliver(
&self,
kind: ApprovalNoticeKind,
route: &str,
notice: &GateNotice<'_>,
) -> anyhow::Result<()> {
let (channel_key, msg) = build_delivery(kind, route, notice)?;
let Some(channel) = self.channels.get(&channel_key).cloned() else {
// A misconfigured route (names a channel that isn't configured) is a realView on GitHub (pinned to 88bb9c8533)
Solutions
- Set the route to 'channel:recipient' with both halves, e.g. approval_route = "discord.ops:123456789".
- Quote the value in YAML/ TOML so the colon survives parsing intact.
- Confirm the channel half exactly matches a configured channel key (see the next error) and the recipient is a real id for that channel.
- Add a startup config test that validates every approval route matches ^[^:\s]+:[^:\s]+$.
Example fix
# before [approval] route = "discord.ops" # after [approval] route = "discord.ops:123456789"
Defensive patterns
Strategy: validation
Validate before calling
fn parse_route(route: &str) -> Option<(&str, &str)> {
let (ch, recv) = route.split_once(':')?;
let ok = |s: &str| !s.trim().is_empty() && !s.contains(':');
(ok(ch) && ok(recv)).then_some((ch.trim(), recv.trim()))
}
assert!(parse_route(&config.approval.route).is_some(), "route must be 'channel:recipient'"); Type guard
fn is_valid_route(route: &str) -> bool {
route.split_once(':')
.map(|(c, r)| !c.is_empty() && !r.is_empty())
.unwrap_or(false)
} Try / catch
match approval_router.deliver(kind, route, ¬ice).await {
Err(e) if e.to_string().contains("is not 'channel:recipient'") => {
eprintln!("config error: approval route {route:?} must be 'channel:recipient'");
}
rest => rest?,
} Prevention
- Validate every approval route against ^[^:\s]+:[^:\s]+$ in a startup config check.
- Quote route strings explicitly in YAML/TOML so the colon is preserved verbatim.
- Never ship placeholder routes — fail config load on TODO/empty values.
When it happens
Trigger: A gate fires a notice with approval_route set to something like "discord.ops" (missing ':recipient'), " :123" or "discord.ops:" (empty half), or a bare recipient id. parse_approval_route returns None and build_delivery bails.
Common situations: Hand-edited config omitting the recipient after a colon; YAML quoting that drops or merges the colon; placeholder values never replaced; operators assuming the channel default recipient is implied.
Related errors
- approval route channel '{channel_key}' is not a configured c
- approval route channel '{channel_key}' does not support outb
- discord send has no recipient: message.recipient is empty an
- git channel `events` routing table ignores every event type;
- matrix: `homeserver` is required
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/ed46b6caaf96b76e.
Report an issue: GitHub.