nautechsystems/nautilus_trader · error · anyhow::Error
Not a subscription channel: {kind}
Error message
Not a subscription channel: {kind} What it means
The Hyperliquid WebSocket adapter validates that the requested channel kind can actually be subscribed to. Response-only channels — SubscriptionResponse, User, Post, Pong, and Error — are message categories the server sends, not topics a client can subscribe to, so passing any of them to the subscription-topic conversion bails with this error. It is a fail-fast guard preventing an invalid subscription request from being sent.
Source
Thrown at crates/adapters/hyperliquid/src/websocket/client.rs:2409
})
}
HyperliquidWsChannel::UserTwapSliceFills => Ok(SubscriptionRequest::UserTwapSliceFills {
user: rest.context("Missing user")?.to_string(),
}),
HyperliquidWsChannel::UserTwapHistory => Ok(SubscriptionRequest::UserTwapHistory {
user: rest.context("Missing user")?.to_string(),
}),
HyperliquidWsChannel::Bbo => Ok(SubscriptionRequest::Bbo {
coin: Ustr::from(rest.context("Missing coin")?),
}),
// Response-only channels are not valid subscription topics
HyperliquidWsChannel::SubscriptionResponse
| HyperliquidWsChannel::User
| HyperliquidWsChannel::Post
| HyperliquidWsChannel::Pong
| HyperliquidWsChannel::Error => {
anyhow::bail!("Not a subscription channel: {kind}")
}
}
}
#[cfg(test)]
mod tests {
use nautilus_live::{SocketReconnectRegistry, SocketReconnectRequestOutcome};
use nautilus_model::identifiers::ClientId;
use nautilus_network::mode::ReconnectRequestOutcome;
use rstest::rstest;
use ustr::Ustr;
use super::*;
use crate::{
common::{
consts::{HYPERLIQUID_WS_POST_INFLIGHT_MAX, HYPERLIQUID_WS_SUBSCRIPTIONS_MAX},
enums::HyperliquidBarInterval,
},View on GitHub (pinned to 18893faf8b)
Solutions
- Use a valid subscription channel (e.g. trades, l2Book, allMids, candle, userEvents) instead of the response-only variant.
- Add a whitelist/filter before subscribing that rejects SubscriptionResponse, User, Post, Pong, and Error.
- If the channel comes from user config, validate/parse it into a dedicated subscribable-channels enum at config-load time.
Example fix
// before
let channel: HyperliquidWsChannel = value.parse()?;
client.subscribe(channel, args).await?;
// after
let channel: HyperliquidWsChannel = value.parse()?;
if !matches!(channel,
HyperliquidWsChannel::SubscriptionResponse
| HyperliquidWsChannel::User
| HyperliquidWsChannel::Post
| HyperliquidWsChannel::Pong
| HyperliquidWsChannel::Error)
{
client.subscribe(channel, args).await?;
} Defensive patterns
Strategy: validation
Validate before calling
const SUBSCRIBABLE: &[HyperliquidWsChannel] = &[
HyperliquidWsChannel::Trades,
HyperliquidWsChannel::L2Book,
HyperliquidWsChannel::AllMids,
HyperliquidWsChannel::Candle,
];
fn is_subscribable(ch: &HyperliquidWsChannel) -> bool {
SUBSCRIBABLE.contains(ch)
} Type guard
fn is_response_only(ch: &HyperliquidWsChannel) -> bool {
matches!(ch,
HyperliquidWsChannel::SubscriptionResponse
| HyperliquidWsChannel::User
| HyperliquidWsChannel::Post
| HyperliquidWsChannel::Pong
| HyperliquidWsChannel::Error)
} Prevention
- Keep config channel strings limited to the documented subscribable channels
- Parse user/config channel names through a dedicated subscribable-channels parser
- Unit-test subscription construction against a whitelist of valid channels
When it happens
Trigger: Calling the subscribe API (or building a HyperliquidSubscription) with channel = SubscriptionResponse, User, Post, Pong, or Error. Typically happens when code maps a generic channel enum or a user-supplied string into HyperliquidWsChannel without filtering response-only variants.
Common situations: Constructing subscriptions from parsed config where the channel string resolves to a response-only variant; writing generic routing code that forwards every channel enum to subscribe; typos or misunderstanding of which Hyperliquid channels are subscribable vs. reply-only.
Related errors
- Invalid Hyperliquid outcome symbol '{symbol}': encoding must
- Unsupported order type for Hyperliquid: {order.order_type():
- Failed to send subscribe command: {e}
- expected 2 impact prices, received {len}
- PolymarketRtdsCryptoTwap metadata['window_seconds'] must be
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/ec37043199a32b61.
Report an issue: GitHub.