linera-io/linera-protocol · error · anyhow
Notification subscription failed: {errors:?}
Error message
Notification subscription failed: {errors:?} What it means
Raised by the NodeExt test wrapper in linera-service (cli_wrappers/wallet.rs) when the GraphQL websocket subscription `subscription { notifications(chainId: ...) }` against a node's `ws://localhost:{port}/ws` endpoint returns an `errors` array in a message payload. The websocket handshake and `connection_init`/`connection_ack` succeeded, but the node rejected or failed the `start` of the subscription itself. The error surfaces asynchronously as an `Err` item on the returned notification stream, not from the initial `notifications()` call.
Source
Thrown at linera-service/src/cli_wrappers/wallet.rs:2079
text == "{\"type\":\"connection_ack\"}",
"Unexpected response: {text}"
);
let query_json = json!({
"id": "1",
"type": "start",
"payload": {
"query": query,
"variables": {},
"operationName": null
}
});
websocket.send(query_json.to_string().into()).await?;
Ok(Box::pin(websocket.map_err(anyhow::Error::from).and_then(
|message| async {
let text = message.into_text()?;
let value: Value = serde_json::from_str(&text).context("invalid JSON")?;
if let Some(errors) = value["payload"].get("errors") {
bail!("Notification subscription failed: {errors:?}");
}
serde_json::from_value(value["payload"]["data"]["notifications"].clone())
.context("Failed to deserialize notification")
},
)))
}
/// Subscribes to query results via the `queryResult` GraphQL subscription.
pub async fn query_result(
&self,
name: &str,
chain_id: ChainId,
application_id: &ApplicationId,
) -> Result<Pin<Box<impl Stream<Item = Result<Value>>>>> {
let query = format!(
r#"subscription {{ queryResult(name: "{name}", chainId: "{chain_id}", applicationId: "{application_id}") }}"#,
);
let url = format!("ws://localhost:{}/ws", self.port);View on GitHub (pinned to 6c226ddcb3)
Solutions
- Verify the chain is known to this node first with a one-shot `query_node` GraphQL query and confirm the chain ID spelling
- Check that the node process behind the wrapper's port is alive and is the expected binary (restart the LocalNodeInstance/fixture if it crashed)
- Rebuild the test harness client and the linera-service node binary from the same commit to remove schema skew
- Drop the broken stream and re-subscribe with `notifications(chain_id)` — subscriptions are stateful across node restarts
Example fix
// before
let mut stream = node.notifications(chain_id).await?;
while let Some(item) = stream.next().await {
let notification = item?; // error arrives here mid-stream
}
// after
let mut stream = node.notifications(chain_id).await?;
while let Some(item) = stream.next().await {
let notification = item.context("notification stream ended with error")?;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Rust: confirm the chain is known to the node before opening the subscription
let response = node
.query_node(&format!(
"query {{ chains {{ chainId }} }}"
))
.await?;
let known = response["data"]["chains"]
.as_array()
.map(|chains| {
chains
.iter()
.any(|c| c["chainId"].as_str() == Some(&chain_id.to_string()))
})
.unwrap_or(false);
anyhow::ensure!(known, "chain {chain_id} not known to this node"); Try / catch
// Consume the stream defensively: the error arrives as an Err item, not from notifications()
let mut stream = node.notifications(chain_id).await?;
while let Some(item) = stream.next().await {
let notification = match item {
Ok(n) => n,
Err(e) if e.to_string().contains("Notification subscription failed") => {
// server rejected the subscription: verify chain/node state, then re-subscribe once
return Err(e.context("node rejected notifications subscription"));
}
Err(e) => return Err(e),
};
// handle notification
} Prevention
- Always match on each stream item's Result — never unwrap — because this error is delivered asynchronously
- Probe chain existence with a one-shot GraphQL query before subscribing
- Keep the test-harness client and the linera-service node binary on the same commit
- After a node restart mid-test, drop and re-create the subscription instead of reusing the stream
When it happens
Trigger: Calling `node.notifications(chain_id)` where the chain ID does not exist, is not tracked by that validator/shard, or is inactive; subscribing while the node process has restarted or is a different process than the one the wrapper's port refers to; running a wrapper built from a linera revision whose GraphQL subscription schema differs from the node binary under test.
Common situations: End-to-end tests that subscribe before the chain is assigned to the node's shard; version skew between the test client and the linera-service binary; a node that crashed earlier in the test (leaving the stream to error on the next message); chain IDs copy-pasted or constructed incorrectly in fixtures.
Related errors
- Query result subscription failed: {errors:?}
- Query "{}" failed: {}
- expected query to start with 'query', got: {s}
- expected whitespace after 'query' keyword
- expected an operation name after 'query', e.g. 'query MyQuer
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/d2f44ae534988c47.
Report an issue: GitHub.