zeroclaw-labs/zeroclaw · error · anyhow::Error
GET /channels/{id} returned {}: explicit channel_id is not a
Error message
GET /channels/{id} returned {}: explicit channel_id is not accessible to this bot What it means
When `channel_ids` lists explicit IDs, `list_target_channels` resolves each one via `GET /api/v4/channels/{id}` with the bot's bearer token. This error means one lookup returned a non-2xx status: the bot token is not valid (401), the bot is not a member of that (private) channel (403), or the ID does not exist / was deleted (404).
Source
Thrown at crates/zeroclaw-channels/src/mattermost.rs:212
.filter(|id| seen.insert(id.clone()))
.collect();
if ids.is_empty() { None } else { Some(ids) }
}
pub(crate) async fn list_target_channels(&self) -> Result<Vec<TargetChannel>> {
let token = self.token().await?.to_string();
if let Some(ids) = self.scoped_channel_ids() {
let mut out = Vec::with_capacity(ids.len());
for id in ids {
let resp = self
.http_client()
.get(format!("{}/api/v4/channels/{}", self.base_url, id))
.bearer_auth(&token)
.send()
.await
.with_context(|| format!("GET /channels/{id} failed"))?;
if !resp.status().is_success() {
bail!(
"GET /channels/{id} returned {}: explicit channel_id is not accessible to this bot",
resp.status()
);
}
let body: serde_json::Value = resp
.json()
.await
.with_context(|| format!("decode /channels/{id} body"))?;
let ty = body.get("type").and_then(|v| v.as_str()).unwrap_or("");
out.push(TargetChannel {
id,
is_direct: is_direct_channel(ty),
});
}
return Ok(out);
}
let resp = self
.http_client()View on GitHub (pinned to 88bb9c8533)
Solutions
- Verify the failing ID: `curl -H "Authorization: Bearer <token>" <server>/api/v4/channels/<id>` and check the status
- Add the bot to the channel (UI "Add members" or `POST /api/v4/channels/{id}/members` with the bot's user ID)
- If status is 401, regenerate/fix the bot token first
- Replace the explicit ID with `*` (or drop `channel_ids`) to fall back to auto-discovery of channels the bot already sees
Example fix
# before [channels.mattermost.team] channel_ids = ["abc123notjoined"] # after [channels.mattermost.team] channel_ids = ["*"] # auto-discover, or list only channels the bot has joined
Defensive patterns
Strategy: try-catch
Validate before calling
for id in &channel_ids {
let resp = client
.get(format!("{base_url}/api/v4/channels/{id}"))
.bearer_auth(&token)
.send()
.await?;
if !resp.status().is_success() {
eprintln!("channel {id} not accessible: {}", resp.status());
}
} Try / catch
match mm_channel.listen(tx).await {
Err(e) if e.to_string().contains("explicit channel_id is not accessible") => {
// surface which channel_ids entry failed, fix membership or switch to "*"
}
other => other,
} Prevention
- Ensure the bot is a member of every channel listed in channel_ids before enabling the channel
- Add a startup health probe that resolves each explicit channel ID via the REST API
- Prefer auto-discovery ("*" or empty channel_ids) unless explicit scoping is required
When it happens
Trigger: `channels.mattermost.<alias>.channel_ids` contains an inaccessible channel (bot never added, removed, archived, or deleted channel, or a typo'd ID), raised on the first `listen()`, `listen_polling()`, `listen_websocket()`, or direct `list_target_channels()` call.
Common situations: Bot was removed from a private channel after config was written; channel ID copied from a different Mattermost instance; bot account exists but was never invited to the team/channel; personal access token revoked.
Related errors
- GET /users/me/channels returned {}
- login failed ({status}): {body}
- post failed ({status}): {body}
- Mattermost WebSocket authentication handshake timed out
- Mattermost WebSocket closed during authentication: {reason}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/2bfc4d0d33125aa5.
Report an issue: GitHub.