{"record":{"id":"ae2d9e99b18c0160","repo":"astrid-runtime/astrid","slug":"staged-content-writer-is-closed","errorCode":null,"errorMessage":"staged content writer is closed","messagePattern":"staged content writer is closed","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-storage/src/principal_state/staging/writer.rs","lineNumber":162,"sourceCode":"    fn drop(&mut self) {\n        if self.preserve_on_drop {\n            return;\n        }\n        self.file.take();\n        if let Some(path) = self.path.take() {\n            let _ = std::fs::remove_file(path);\n        }\n        self.area\n            .inner\n            .seal_order\n            .lock()\n            .reserved_identifiers\n            .remove(&self.id);\n    }\n}\n\nfn closed_writer() -> std::io::Error {\n    std::io::Error::new(\n        std::io::ErrorKind::BrokenPipe,\n        \"staged content writer is closed\",\n    )\n}\n","sourceCodeStart":144,"sourceCodeEnd":167,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage/src/principal_state/staging/writer.rs#L144-L167","documentation":"The staged content writer was used after being closed, so the library returns a BrokenPipe io::Error. The writer's close operation releases its reservation (reserved_identifiers removal), and any subsequent operation has no valid backing state, so it fails rather than silently dropping data.","triggerScenarios":"Calling any write/flush method on a StagedContentWriter after close() has been called, e.g. writing in two phases where the second write happens after an early close, or reusing a stored writer handle after the close path ran.","commonSituations":"Wrapping the writer in a scope where close is called then more bytes are appended; double-close followed by use; holding the writer in a struct and using it after a cleanup routine closed it.","solutions":["Remove the write call that occurs after close(), or move it before closing","Restructure code so the writer is consumed in a linear order: write all data, then close once","Wrap the writer so Rust ownership prevents reuse after close (consume self in close)","Check application logic for double-close paths (error + success cleanup both closing)"],"exampleFix":"// before\nwriter.write_all(&chunk)?; // after close()\nwriter.close()?;\nwriter.write_all(&more)?;\n// after\nwriter.write_all(&chunk)?;\nwriter.write_all(&more)?;\nwriter.close()?;","handlingStrategy":"try-catch","validationCode":"// Rust: track closed state in a wrapper\nstruct GuardedWriter { w: Option<StagedContentWriter> }\nimpl GuardedWriter {\n    fn write(&mut self, b: &[u8]) -> io::Result<()> {\n        match &mut self.w { Some(w) => w.write_all(b), None => Ok(()) } // no-op after close\n    }\n}","typeGuard":"fn is_open(w: &StagedWriterHandle) -> bool { !w.is_closed() } // expose/check a closed flag before use","tryCatchPattern":"match writer.write_all(&data) {\n    Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => { /* writer already closed: drop or reopen */ }\n    other => other?,\n}","preventionTips":["Consume the writer in close(self) so the compiler prevents later use","Enforce linear write-then-close flow in one function or scope","Never store writers in long-lived structs across close points","Add debug_assert!(!closed) in wrappers around the writer"],"tags":["io","broken-pipe","use-after-close","storage"],"backgroundTag":"broken-pipe","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}