{"record":{"id":"8f9b7db4b2be758b","repo":"vectordotdev/vector","slug":"indexer-acknowledgements-channel-must-allow-at-lea","errorCode":null,"errorMessage":"Indexer acknowledgements channel must allow at least one pending ack","messagePattern":"Indexer acknowledgements channel must allow at least one pending ack","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/sources/splunk_hec/acknowledgements.rs","lineNumber":219,"sourceCode":"    ack_event_finalizer: UnorderedFinalizer<u64>,\n}\n\nimpl Channel {\n    fn new(max_pending_acks_per_channel: u64, shutdown: ShutdownSignal) -> Self {\n        let ack_ids_status = Arc::new(Mutex::new(RoaringTreemap::new()));\n        let finalizer_ack_ids_status = Arc::clone(&ack_ids_status);\n        let (ack_event_finalizer, mut ack_stream) = UnorderedFinalizer::new(Some(shutdown));\n        crate::spawn_in_current_span(async move {\n            while let Some((status, ack_id)) = ack_stream.next().await {\n                if status == BatchStatus::Delivered {\n                    let mut ack_ids_status = finalizer_ack_ids_status.lock().unwrap();\n                    ack_ids_status.insert(ack_id);\n                    if ack_ids_status.len() > max_pending_acks_per_channel {\n                        match ack_ids_status.min() {\n                            Some(min) => ack_ids_status.remove(min),\n                            // max pending acks per channel is guaranteed to be >= 1,\n                            // thus there must be at least one ack id available to remove\n                            None => unreachable!(\n                                \"Indexer acknowledgements channel must allow at least one pending ack\"\n                            ),\n                        };\n                    }\n                }\n            }\n        });\n\n        Self {\n            last_used_timestamp: RwLock::new(Instant::now()),\n            currently_available_ack_id: AtomicU64::new(0),\n            ack_ids_status,\n            ack_event_finalizer,\n        }\n    }\n\n    fn get_ack_id(&self, batch_rx: BatchStatusReceiver) -> u64 {\n        {","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/vectordotdev/vector/blob/3708c39b12a93212ed8b8d7510b4cc7769cb5864/src/sources/splunk_hec/acknowledgements.rs#L201-L237","documentation":"The splunk_hec acknowledgement tracker caps pending ack ids per channel; when len() exceeds the cap it removes the smallest (oldest) id. The removal is guarded by min() returning None => unreachable, because the config type (NonZeroU64, default 1_000_000) guarantees max_pending_acks_per_channel >= 1 and the set is non-empty whenever len() > cap - the branch cannot fire unless that guarantee is broken.","triggerScenarios":"Only if max_pending_acks_per_channel is 0 or the set/cap invariant breaks: the NonZeroU64 config type prevents 0 through YAML, so in practice this requires code constructing Channel::new with a computed 0 capacity or corrupting ack_ids_status (custom builds, refactors bypassing AcknowledgementsConfig).","commonSituations":"Custom embeddings building the acknowledgements Channel directly with an arithmetically computed capacity (integer division/underflow to 0); modifications to the ack-id set logic; unreachable through stock configuration.","solutions":["If embedding, construct via AcknowledgementsConfig (NonZeroU64) or pass a value >= 1 to Channel::new","Audit any code computing the cap arithmetically before it reaches the channel","In stock Vector, report it - the NonZeroU64 invariant makes this a bug by definition","Capture RUST_BACKTRACE=1 output when it fires to identify which construction path passed 0"],"exampleFix":"// before\nlet channel = Channel::new(computed_cap, shutdown); // computed_cap == 0 -> invariant broken\n\n// after\nlet cap = std::num::NonZeroU64::new(computed_cap.max(1)).unwrap();\nlet config = AcknowledgementsConfig { max_pending_acks_per_channel: cap, ..Default::default() };","handlingStrategy":"validation","validationCode":"// Before enabling acknowledgements, assert the invariant yourself:\nassert!(u64::from(config.max_pending_acks_per_channel) >= 1); // NonZeroU64 makes this static\n// and never call Channel::new directly with a computed value below 1","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always source the cap from AcknowledgementsConfig (NonZeroU64), never raw arithmetic","Keep the default (1_000_000) unless indexer behavior demands otherwise","Treat this panic as a code bug: capture RUST_BACKTRACE=1 and report"],"tags":["rust","vector","splunk-hec","acknowledgements","panic","invariant"],"backgroundTag":"splunk-ack-invariant-violation","analyzedSha":"3708c39b12a93212ed8b8d7510b4cc7769cb5864","analyzedAt":"2026-08-20T07:02:18.786Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}