{"record":{"id":"f788fa3e37e6bddc","repo":"benbjohnson/litestream","slug":"close-txid-file-w","errorCode":null,"errorMessage":"close txid file: %w","messagePattern":"close txid file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"replica.go","lineNumber":1740,"sourceCode":"\ttmpPath := txidPath + \".tmp\"\n\n\tf, err := os.Create(tmpPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create txid temp file: %w\", err)\n\t}\n\tdefer f.Close()\n\tdefer os.Remove(tmpPath)\n\n\tif _, err := fmt.Fprintln(f, txid); err != nil {\n\t\treturn fmt.Errorf(\"write txid: %w\", err)\n\t}\n\n\tif err := f.Sync(); err != nil {\n\t\treturn fmt.Errorf(\"sync txid file: %w\", err)\n\t}\n\n\tif err := f.Close(); err != nil {\n\t\treturn fmt.Errorf(\"close txid file: %w\", err)\n\t}\n\n\tif err := os.Rename(tmpPath, txidPath); err != nil {\n\t\treturn fmt.Errorf(\"rename txid file: %w\", err)\n\t}\n\n\tif err := internal.FsyncDir(filepath.Dir(txidPath)); err != nil {\n\t\treturn fmt.Errorf(\"sync txid dir: %w\", err)\n\t}\n\treturn nil\n}\n\n// ReadTXIDFile reads the TXID from a sidecar file at <outputPath>-txid.\n// Returns 0, nil if the file does not exist (first run).\nfunc ReadTXIDFile(outputPath string) (ltx.TXID, error) {\n\ttxidPath := TXIDPath(outputPath)\n\n\tdata, err := os.ReadFile(txidPath)","sourceCodeStart":1722,"sourceCodeEnd":1758,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/replica.go#L1722-L1758","documentation":"WriteTXIDFile wraps f.Close() as 'close txid file: %w'. Close is called after fsync to release the file descriptor for the temp file before the atomic rename. A close error means buffered metadata could not be committed or the descriptor could not be released, so the temp file may be incomplete and the TXID is NOT promoted to the final '<outputPath>-txid' path.","triggerScenarios":"Calling WriteTXIDFile when closing '<outputPath>-txid.tmp' fails: delayed writeback errors surfacing at close, out-of-file-descriptors (EMFILE), or deferred f.Close() earlier in the function having already interacted oddly with the same descriptor on some platforms.","commonSituations":"File-descriptor exhaustion in long-running litestream processes; disk errors reported at close time after writeback; container limits (ulimit -n) too low; Windows/SMB shares returning errors on close.","solutions":["Check for file-descriptor exhaustion (ulimit -n) and raise it or fix descriptor leaks in the host process","Inspect system logs for device writeback errors and repair the storage volume","Remove stale '<outputPath>-txid.tmp' files and retry the operation","Move the output path onto a local, POSIX-compliant filesystem"],"exampleFix":"// before: ignoring close errors in your own temp-file code\nf.Close()\nos.Rename(tmpPath, finalPath)\n// after\nif err := f.Close(); err != nil {\n    os.Remove(tmpPath)\n    return fmt.Errorf(\"close temp file: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"// Check descriptor headroom before batch operations\nif rlim := getFDLimit(); rlim < 1024 { raiseFDLimitOrWarn() }","typeGuard":"func fdBudgetOK() bool { return countOpenFDs() < fdLimit()-64 }","tryCatchPattern":"if err := WriteTXIDFile(outputPath, txid); err != nil {\n    if errors.Is(err, syscall.EMFILE) {\n        // raise ulimit -n or fix fd leaks, then retry\n    }\n    return err\n}","preventionTips":["Raise ulimit -n for long-running litestream processes","Watch system logs for writeback/I/O errors","Avoid SMB/network shares for database paths","Retry the write; the temp file is auto-cleaned on failure"],"tags":["filesystem","file-close","durability","litestream"],"backgroundTag":"file-write-failed","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}