zeroclaw-labs/zeroclaw · error · anyhow::Error

approval route channel '{channel_key}' is not a configured c

Error message

approval route channel '{channel_key}' is not a configured channel (route '{route}')

What it means

The approval route parsed fine, but its channel half names a channel key that is not present in the runtime's channel registry (self.channels). A misconfigured route is treated as a real operator error: deliver() returns Err so the broker logs it. It never affects the gate itself — the broker's deliver wrappers only log.

Source

Thrown at crates/zeroclaw-runtime/src/sop/approval/channel_route.rs:377

        );
    };
    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 real
            // operator error worth surfacing: return Err so the broker logs it. It
            // still never affects the gate (the broker's deliver_* wrappers only log).
            anyhow::bail!(
                "approval route channel '{channel_key}' is not a configured channel \
                 (route '{route}')"
            );
        };
        // An inbound-only channel's `send` is a no-op that returns `Ok`, so spawning it
        // would report success without delivering anything. Refuse and surface it (the
        // broker logs the Err) rather than silently dropping the notice.
        if !channel.supports_outbound_send() {
            anyhow::bail!(
                "approval route channel '{channel_key}' does not support outbound \
                 delivery (it is inbound-only); its approval notice cannot be sent \
                 (route '{route}')"
            );
        }
        // Fire-and-forget: hand the async send to the runtime and return. The gate is
        // never blocked on channel I/O; a send failure is logged in the task.
        // Native gate prompt first (buttons / keyboards, answered through the
        // channel's inbound path); channels without one fall back to the text

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Fix the route's channel half to exactly match a configured channel key (list your channels config).
  2. Or add/enable the channel under the key the route names.
  3. Keep route and channel keys in one place — derive route strings from channel keys rather than retyping them.
  4. Add a config lint that cross-checks every approval route channel against configured channel keys at startup.

Example fix

# before
[channels.discord]
token = "..."
[approval]
route = "discord.ops:123456789"   # no channel keyed 'discord.ops'

# after
[channels.discord.ops]
token = "..."
[approval]
route = "discord.ops:123456789"
Defensive patterns

Strategy: validation

Validate before calling

let (channel_key, _) = route.split_once(':').expect("validated route");
assert!(
    channels.contains_key(channel_key),
    "approval route channel '{channel_key}' is not configured"
);

Type guard

fn route_channel_configured(route: &str, channels: &ChannelMap) -> bool {
    route.split_once(':').map(|(c, _)| channels.contains_key(c)).unwrap_or(false)
}

Try / catch

match router.deliver(kind, route, &notice).await {
    Err(e) if e.to_string().contains("is not a configured channel") => {
        log::warn!("operator error: route {route} names an unconfigured channel; fix config");
    }
    rest => rest?,
}

Prevention

When it happens

Trigger: deliver() with a route like "discord.ops:123" when no channel keyed "discord.ops" is configured — e.g. the channel section was renamed, disabled, or the route was copied from another deployment.

Common situations: Channel renamed in config but approval route left stale; channel section commented out for testing; environment-specific channel keys (discord-prod vs discord-ops) mismatched; typo in the channel half of the route.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/821d3d9758f10812. Report an issue: GitHub.