FuelLabs/fuel-core · error · anyhow::Error

Stream closed without transaction status

Error message

Stream closed without transaction status

What it means

Thrown by QueryService::submit_and_await_commit (crates/fuel-core/src/service/query.rs:57). The node subscribes to status updates for the transaction id, submits the tx, then awaits the next final status on the filtered stream. The stream returning None means the status-update channel closed before any final status was delivered — in practice the tx status manager or the node itself was dropped or shut down mid-wait.

Source

Thrown at crates/fuel-core/src/service/query.rs:76

        &self,
        tx: Transaction,
    ) -> anyhow::Result<TransactionStatus> {
        let id = tx.id(&self
            .shared
            .config
            .snapshot_reader
            .chain_config()
            .consensus_parameters
            .chain_id());
        let stream = self.transaction_status_change(id).await?.filter(|status| {
            futures::future::ready(status.as_ref().is_ok_and(|status| status.is_final()))
        });
        futures::pin_mut!(stream);
        self.submit(tx).await?;
        stream
            .next()
            .await
            .ok_or_else(|| anyhow::anyhow!("Stream closed without transaction status"))?
    }

    /// Return a stream of status changes for a transaction.
    pub async fn transaction_status_change(
        &self,
        id: Bytes32,
    ) -> anyhow::Result<impl Stream<Item = anyhow::Result<TransactionStatus>> + '_> {
        // First subscribe to the statuses, and only after that create a view.
        let tx_status_manager = &self.shared.tx_status_manager;
        let rx = tx_status_manager.tx_update_subscribe(id).await?;
        let db = self.shared.database.off_chain().latest_view()?;
        let state = StatusChangeState {
            db,
            tx_status_manager,
        };
        Ok(transaction_status_change(state, rx, id, true).await)
    }
}

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Check node liveness and logs first — a shutdown or panic during the await is the usual cause; restart the node.
  2. Retry the submission after restart: submitting the same signed transaction is idempotent (same tx id), then await the status again.
  3. Use transaction_status_change (the raw stream API) and handle closure explicitly when you need custom retry logic instead of submit_and_await_commit.
  4. Wrap the await in a client-side timeout so node shutdowns surface as timeouts rather than dangling awaits.

Example fix

// before: single shot, fails when the node restarts mid-await
let status = client.submit_and_await_commit(tx).await?;

// after: retry on stream closure (same signed tx -> same id, safe to resubmit)
loop {
    match client.submit_and_await_commit(tx.clone()).await {
        Ok(status) => break status,
        Err(e) if e.to_string().contains("Stream closed") => {
            tokio::time::sleep(Duration::from_secs(1)).await;
            continue;
        }
        Err(e) => return Err(e),
    }
}
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight liveness check before submitting
async fn node_alive(client: &fuel_client::client::FuelClient) -> bool {
    client.health().await.is_ok()
}

if !node_alive(&client).await {
    return Err(anyhow::anyhow!("node unreachable before submit"));
}

Try / catch

loop {
    match client.submit_and_await_commit(tx.clone()).await {
        Ok(status) => break Ok(status),
        Err(e) if e.to_string().contains("Stream closed without transaction status") => {
            // node likely restarted; wait, verify health, then resubmit the same signed tx
            tokio::time::sleep(std::time::Duration::from_secs(1)).await;
            continue;
        }
        Err(e) => break Err(e),
    }
}

Prevention

When it happens

Trigger: The node shuts down (SharedState or tx_status_manager dropped) while the caller is awaiting commit; a restart lands between submission and final status; abnormal service teardown during heavy load.

Common situations: Automation that stops the node right after submitting a transaction; container or pod restarts mid-await; clients holding the future across process boundaries; changes in status manager lifecycle across fuel-core versions.

Related errors


AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16). Data as JSON: /api/errors/81ec63b5c0bc22ae. Report an issue: GitHub.