{"record":{"id":"7d4135620cb5d03c","repo":"benbjohnson/litestream","slug":"rename-to-output-path-w","errorCode":null,"errorMessage":"rename to output path: %w","messagePattern":"rename to output path: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"replica.go","lineNumber":1157,"sourceCode":"\t}\n\n\t// Create temp file for restore.\n\ttmpPath := opt.OutputPath + \".tmp\"\n\tdefer func() { _ = os.Remove(tmpPath) }()\n\n\t// Download and decompress snapshot.\n\tif err := r.downloadSnapshotV3(ctx, client, snapshot.Generation, snapshot.Index, tmpPath); err != nil {\n\t\treturn fmt.Errorf(\"download snapshot: %w\", err)\n\t}\n\n\t// Apply WAL segments.\n\tif err := r.applyWALSegmentsV3(ctx, client, snapshot.Generation, snapshot.Index, segments, tmpPath); err != nil {\n\t\treturn fmt.Errorf(\"apply WAL segments: %w\", err)\n\t}\n\n\t// Rename to final path.\n\tif err := os.Rename(tmpPath, opt.OutputPath); err != nil {\n\t\treturn fmt.Errorf(\"rename to output path: %w\", err)\n\t}\n\tif err := internal.FsyncDir(filepath.Dir(opt.OutputPath)); err != nil {\n\t\treturn fmt.Errorf(\"sync restore output dir: %w\", err)\n\t}\n\n\tif opt.IntegrityCheck != IntegrityCheckNone {\n\t\tif err := checkIntegrity(ctx, opt.OutputPath, opt.IntegrityCheck); err != nil {\n\t\t\tif ctx.Err() == nil {\n\t\t\t\t_ = os.Remove(opt.OutputPath)\n\t\t\t\t_ = os.Remove(opt.OutputPath + \"-shm\")\n\t\t\t\t_ = os.Remove(opt.OutputPath + \"-wal\")\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"post-restore integrity check: %w\", err)\n\t\t}\n\t\tr.Logger().Info(\"post-restore integrity check passed\")\n\t}\n\n\treturn nil","sourceCodeStart":1139,"sourceCodeEnd":1175,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/replica.go#L1139-L1175","documentation":"Once the fully restored database is assembled in the temp file, RestoreV3 atomically renames it from tmpPath to opt.OutputPath via os.Rename and wraps failures as 'rename to output path: %w'. At this point the data is downloaded and replayed; only the final placement failed.","triggerScenarios":"Calling Replica.Restore when the final rename fails: the destination filesystem differs from the temp file's (cross-device rename is impossible if tmpPath and OutputPath resolve to different mounts), a file appeared at OutputPath during the restore, or permission problems on the destination directory.","commonSituations":"OutputPath is a symlink or bind mount pointing to another filesystem than the temp file location; another process created OutputPath during a long restore (race with the earlier exists-check); restore run inside a container where /tmp and the destination are separate mounts and the temp path resolves differently; destination directory made read-only mid-restore.","solutions":["Ensure opt.OutputPath's directory is writable and no file appeared at that path during the restore; remove the blocker and retry","Avoid symlinked or mount-pointed output paths that resolve to a different filesystem; restore directly onto the target mount","Read the wrapped cause: EEXIST means something created the path mid-restore, EXDEV means cross-device rename, EACCES means permissions","If EEXIST, the .tmp file still holds a complete restore (before defer cleanup); consider recovering from it after verifying integrity"],"exampleFix":"// before\nopt := litestream.RestoreOptions{OutputPath: \"/mnt/data/db.sqlite\"} // /mnt/data is a symlink to another device\n// after\nreal, err := filepath.EvalSymlinks(\"/mnt/data\") // resolve to same filesystem as temp dir\nif err != nil {\n    return err\n}\nopt := litestream.RestoreOptions{OutputPath: filepath.Join(real, \"db.sqlite\")}","handlingStrategy":"validation","validationCode":"dir := filepath.Dir(outputPath)\n// resolve symlinks so temp file and destination end up on the same filesystem\ndirReal, err := filepath.EvalSymlinks(dir)\nif err != nil {\n    return err\n}\nif _, err := os.Stat(outputPath); err == nil {\n    return fmt.Errorf(\"%s appeared mid-restore; remove it first\", outputPath)\n}","typeGuard":null,"tryCatchPattern":"if err := replica.Restore(ctx, opt); err != nil {\n    if strings.Contains(err.Error(), \"rename to output path\") {\n        if strings.Contains(errors.Unwrap(err).Error(), \"file exists\") {\n            return fmt.Errorf(\"%s was created during restore; remove and retry\", opt.OutputPath)\n        }\n        if strings.Contains(errors.Unwrap(err).Error(), \"invalid argument\") || strings.Contains(errors.Unwrap(err).Error(), \"cross\") {\n            return fmt.Errorf(\"temp and output are on different filesystems; choose OutputPath on the same mount\")\n        }\n    }\n    return err\n}","preventionTips":["Point OutputPath directly at the real (non-symlink) target filesystem","Ensure no other process writes to the output path during the restore","Verify the destination directory's permissions before starting a long restore","Restore onto the same mount/volume where the temp file is created to guarantee atomic rename"],"tags":["go","restore","filesystem","rename","atomic"],"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-14T00:17:10.932Z"}