{"record":{"id":"4e1731ad51569816","repo":"influxdata/influxdb","slug":"must-have-offset","errorCode":null,"errorMessage":"must have offset","messagePattern":"must have offset","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"info","filePath":"core/table_batch/src/builder/column_writer/string.rs","lineNumber":40,"sourceCode":"            offsets: vec![0, value.len() as u32],\n        })\n    }\n\n    pub(crate) fn push_str(&mut self, s: &str) {\n        self.0.values.push_str(s);\n        self.0.offsets.push(self.0.values.len() as u32);\n    }\n\n    /// Remove the last wrote value and return it.\n    ///\n    /// This call does not panic when empty.\n    pub(crate) fn pop(&mut self) -> Option<String> {\n        if self.0.offsets.len() < 2 {\n            return None;\n        }\n\n        self.0.offsets.pop();\n        let new_len = *self.0.offsets.last().expect(\"must have offset\") as usize;\n\n        let last_val = self.0.values.split_at(new_len).1.to_string();\n        self.0.values.truncate(new_len);\n\n        Some(last_val)\n    }\n\n    pub(crate) fn len(&self) -> usize {\n        self.0.offsets.len() - 1\n    }\n\n    pub(crate) fn finish(self) -> Option<PackedStrings> {\n        if self.0.offsets.len() < 2 {\n            None\n        } else {\n            Some(self.0)\n        }\n    }","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/influxdata/influxdb/blob/d28e26e048401c53cbb98cf2d6ab0cf1e98048ca/core/table_batch/src/builder/column_writer/string.rs#L22-L58","documentation":"StringBuffer::pop() removes and returns the last packed string. It first returns None when offsets.len() < 2 (buffer empty), then pops one offset and reads the new tail offset to know where the previous string ends. At that point offsets is guaranteed to contain at least the initial 0 offset, so .expect('must have offset') is a defensive guard that is unreachable in correct code — the len < 2 check already established at least two offsets existed before the pop.","triggerScenarios":"Calling pop() on a StringBuffer; the expect itself cannot fire through the public flow because offsets always retains its initial 0 element (Default initializes offsets to vec![0] and every push appends). It could only fire if the pub(crate) PackedStrings field was mutated directly elsewhere leaving offsets empty.","commonSituations":"Essentially never observed; the reachable sibling failure is calling pop() on an empty buffer, which correctly returns None. If this message appears, look for code reaching into the pub(crate) tuple field (.0) and truncating offsets directly, bypassing push_str/pop.","solutions":["If hit, grep for direct manipulation of the .0 / PackedStrings fields (values/offsets) outside StringBuffer and route all mutation through push_str/pop.","Treat it as an internal bug in core/table_batch and report with the write/rollback sequence that preceded it.","Keep pop()'s Option-based API: callers should already handle None, so no caller change is needed."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// pop() already returns Option on empty — use the Option API instead of assuming:\nif let Some(last) = buffer.pop() {\n    // handle last\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not reach into the pub(crate) PackedStrings field (.0) to mutate values/offsets directly; use push_str/pop only.","Rely on pop()'s Option return for empty-buffer handling — the expect itself is unreachable in correct code."],"tags":["rust","influxdb","internal-invariant","string-buffer","panic"],"backgroundTag":"internal-invariant-violation","analyzedSha":"d28e26e048401c53cbb98cf2d6ab0cf1e98048ca","analyzedAt":"2026-08-16T19:53:34.623Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}