{"record":{"id":"d6a66c8f95d54038","repo":"gastownhall/beads","slug":"atomicfile-close-w","errorCode":null,"errorMessage":"atomicfile: close: %w","messagePattern":"atomicfile: close: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/atomicfile/atomicfile.go","lineNumber":90,"sourceCode":"\t}\n\tw.done = true\n\n\t// Ensure permissions before rename — CreateTemp uses 0600 by default.\n\tif err := w.f.Chmod(w.perm); err != nil {\n\t\t_ = w.f.Close()\n\t\t_ = os.Remove(w.f.Name())\n\t\treturn fmt.Errorf(\"atomicfile: chmod: %w\", err)\n\t}\n\n\tif err := w.f.Sync(); err != nil {\n\t\t_ = w.f.Close()\n\t\t_ = os.Remove(w.f.Name())\n\t\treturn fmt.Errorf(\"atomicfile: sync: %w\", err)\n\t}\n\n\tif err := w.f.Close(); err != nil {\n\t\t_ = os.Remove(w.f.Name())\n\t\treturn fmt.Errorf(\"atomicfile: close: %w\", err)\n\t}\n\n\tif err := os.Rename(w.f.Name(), w.target); err != nil {\n\t\t_ = os.Remove(w.f.Name())\n\t\treturn fmt.Errorf(\"atomicfile: rename: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// Abort discards the temp file without renaming. The target is untouched.\n// Safe to call multiple times or after Close.\nfunc (w *Writer) Abort() error {\n\tif w.done {\n\t\treturn nil\n\t}\n\tw.done = true\n\t_ = w.f.Close()","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/atomicfile/atomicfile.go#L72-L108","documentation":"After chmod and sync succeed, Writer.Close calls f.Close(). If closing the file descriptor reports an error (e.g. deferred write errors surfaced at close, or the fd was already closed), the temp file is removed and this wrapped error is returned; the target stays intact.","triggerScenarios":"Calling Close twice on the same Writer, or the OS returning an error at close time (delayed I/O error, fd exhaustion artifacts).","commonSituations":"Control-flow bugs where Abort and Close are both called, or code paths that close the underlying file independently of the Writer.","solutions":["Call Close exactly once per Writer (use Abort for the discard path instead)","Check for 'file already closed' in the wrapped error to find a double-close bug","If the error indicates an I/O problem, retry the whole Create/Close sequence"],"exampleFix":"// before\nw.Abort()\nw.Close() // double-close -> \"atomicfile: close\"\n// after\nif err := w.Close(); err != nil { /* handle */ } // only one finalizer path","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := w.Close(); err != nil {\n    if strings.Contains(err.Error(), \"file already closed\") {\n        log.Printf(\"double Close detected: %v\", err)\n    }\n    return err\n}","preventionTips":["Have exactly one ownership path that closes the Writer","Never close w.f directly; use Close/Abort","Audit code paths where both Abort and Close could run"],"tags":["filesystem","io","atomic-write"],"backgroundTag":"file-close-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}