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

  1. Verify the chain is known to this node first with a one-shot `query_node` GraphQL query and confirm the chain ID spelling
  2. Check that the node process behind the wrapper's port is alive and is the expected binary (restart the LocalNodeInstance/fixture if it crashed)
  3. Rebuild the test harness client and the linera-service node binary from the same commit to remove schema skew
  4. 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

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


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/d2f44ae534988c47. Report an issue: GitHub.