{"record":{"id":"347b72d397b5ee7f","repo":"clockworklabs/SpacetimeDB","slug":"invalidinput-347b72","errorCode":"InvalidInput","errorMessage":"refusing to compress mutable segment {head_offset}","messagePattern":"refusing to compress mutable segment (.+?)","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"warning","filePath":"crates/commitlog/src/lib.rs","lineNumber":389,"sourceCode":"    ///\n    /// This method acquires a read lock on this `Commitlog` instance, but\n    /// releases it once the compression work starts. Concurrent compression\n    /// tasks on the same segment are safe, but external coordination is\n    /// required to avoid duplicate work.\n    ///\n    /// Attempting to compress a segment that is already compressed incurs a\n    /// small overhead to open the file and determining its format, but\n    /// otherwise does nothing.\n    pub fn compress_segments(&self, offsets: &[u64]) -> io::Result<CompressionStats> {\n        let (repo, head_offset) = {\n            let inner = self.inner.read().unwrap();\n            let repo = inner.repo.clone();\n            let head_offset = inner.head.min_tx_offset();\n\n            (repo, head_offset)\n        };\n        if offsets.contains(&head_offset) {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                format!(\"refusing to compress mutable segment {head_offset}\"),\n            ));\n        }\n        let mut stats = <_>::default();\n        for offset in offsets {\n            stats += repo.compress_segment(*offset)?;\n        }\n        Ok(stats)\n    }\n\n    /// Remove all data from the log and reopen it.\n    ///\n    /// Log segments are deleted starting from the newest. As multiple segments\n    /// cannot be deleted atomically, the log may not be completely empty if\n    /// the method returns an error.\n    ///\n    /// Note that the method consumes `self` to ensure the log is not modified","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/commitlog/src/lib.rs#L371-L407","documentation":"compress_segments refuses to compress the head segment (the segment currently open for appends, identified by its min_tx_offset) because compressing a mutable segment would race with concurrent writes. This is a deterministic usage guard (InvalidInput), not a side-effect failure: nothing was compressed and no state changed.","triggerScenarios":"Calling log.compress_segments(&offsets) with a list containing the current head segment's base offset. The typical source is passing the unfiltered result of log.existing_segment_offsets(), whose last entry is exactly the mutable head segment.","commonSituations":"A maintenance job that 'compresses all segments' by listing the directory; compressing right after startup before the log has rolled to a new segment.","solutions":["Drop the head segment from the list: head is the maximum of existing_segment_offsets(); pass only offsets < head","Roll to a new segment first (write past max_segment_size or lower it) so the target segment becomes immutable, then compress it","Treat the refusal as a no-op signal and skip that offset"],"exampleFix":"// before\nlet offsets = log.existing_segment_offsets()?;\nlog.compress_segments(&offsets)?; // last entry is the mutable head -> refused\n\n// after\nlet mut offsets = log.existing_segment_offsets()?;\noffsets.pop(); // discard the mutable head segment\nlog.compress_segments(&offsets)?;","handlingStrategy":"validation","validationCode":"let mut offsets = log.existing_segment_offsets()?;\nif let Some(&head) = offsets.last() {\n    offsets.retain(|&o| o != head); // the head segment is still mutable\n}\nlog.compress_segments(&offsets)?;","typeGuard":"fn is_mutable_segment_refusal(e: &io::Error) -> bool {\n    e.kind() == io::ErrorKind::InvalidInput\n        && e.to_string().contains(\"refusing to compress mutable segment\")\n}","tryCatchPattern":"match log.compress_segments(&offsets) {\n    Ok(stats) => { /* ... */ }\n    Err(e) if is_mutable_segment_refusal(&e) => { /* skip the head segment, not an error */ }\n    Err(e) => return Err(e),\n}","preventionTips":["Compress only sealed segments: everything except the maximum offset from existing_segment_offsets()","Run compression from a maintenance path, never on the hot write path"],"tags":["rust","commitlog","compression","segment","invalid-input"],"backgroundTag":"invalid-argument","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}