{"record":{"id":"cd8bf2bee2e5d17d","repo":"FuelLabs/fuel-core","slug":"publish-setup-failed-missing-redis-node-at-index","errorCode":null,"errorMessage":"publish setup failed: missing redis node at index {idx}","messagePattern":"publish setup failed: missing redis node at index (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/fuel-core/src/service/adapters/consensus_module/poa.rs","lineNumber":1157,"sourceCode":"        let n = self.redis_nodes.len();\n        let mut results: Vec<Option<anyhow::Result<WriteBlockResult>>> =\n            (0..n).map(|_| None).collect();\n        let mut written_count = 0usize;\n        let quorum_start = std::time::Instant::now();\n\n        // Share the block payload across spawned tasks via `Arc` so we\n        // clone it once rather than once per node.\n        let block: std::sync::Arc<SealedBlock> = std::sync::Arc::new(block.clone());\n        let block_data: std::sync::Arc<[u8]> = block_data.into();\n\n        let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<(\n            usize,\n            anyhow::Result<WriteBlockResult>,\n        )>();\n        #[allow(clippy::needless_range_loop)]\n        for idx in 0..n {\n            let Some(per_node) = self.per_node_adapter(idx) else {\n                results[idx] = Some(Err(anyhow!(\n                    \"publish setup failed: missing redis node at index {idx}\"\n                )));\n                continue;\n            };\n            let tx = tx.clone();\n            let block = block.clone();\n            let block_data = block_data.clone();\n            // Increment the gauge synchronously here (before the task\n            // is queued) and move the guard into the closure so it\n            // decrements whether the task completes normally, panics,\n            // or is cancelled mid-await.\n            let task_guard = OutstandingPublishTaskGuard::new();\n            tokio::spawn(async move {\n                let _task_guard = task_guard;\n                let result = per_node\n                    .publish_block_on_node(epoch, &block, &block_data)\n                    .await;\n                // Ignored if the receiver was dropped after quorum — the","sourceCodeStart":1139,"sourceCodeEnd":1175,"githubUrl":"https://github.com/FuelLabs/fuel-core/blob/add100d30d21498e8528c46be8567fbd2ea019af/crates/fuel-core/src/service/adapters/consensus_module/poa.rs#L1139-L1175","documentation":"Invariant violation during fan-out setup in publish_block_on_all_nodes: the code indexes self.redis_nodes while preparing per-node write tasks, but the node list no longer contains an entry at the requested index (e.g. the list was empty, truncated, or mutated between length computation and task construction). It is a pre-flight guard that aborts the publish before any Redis write is attempted; no block data has been sent when it fires.","triggerScenarios":"Thrown at crates/fuel-core/src/service/adapters/consensus_module/poa.rs:1157 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Verify the PoA consensus module's redis_nodes configuration contains the expected number of entries before starting the publisher","Rebuild/refresh the node list from configuration at the start of each publish cycle instead of caching a stale index","Log the list length alongside the missing index to distinguish an empty list from an off-by-one index bug","Fail the publish attempt explicitly so the caller (publish_produced_block / repair_sub_quorum_block) can retry or report the misconfiguration"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"add100d30d21498e8528c46be8567fbd2ea019af","analyzedAt":"2026-09-05T18:46:12.018Z","contentChangedAt":"2026-09-05T18:46:12.018Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}