{"record":{"id":"72a7f0679ab6119b","repo":"benbjohnson/litestream","slug":"open-wal-file-w","errorCode":null,"errorMessage":"open wal file: %w","messagePattern":"open wal file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"db.go","lineNumber":1712,"sourceCode":"\tif err != nil {\n\t\treturn info, NewLTXError(\"open\", ltxPath, 0, uint64(exec.pos.TXID), uint64(exec.pos.TXID), err)\n\t}\n\tdefer func() { _ = ltxFile.Close() }()\n\n\tdec := ltx.NewDecoder(ltxFile)\n\tif err := dec.DecodeHeader(); err != nil {\n\t\t// Decode failure indicates corruption\n\t\tltxErr := NewLTXError(\"decode\", ltxPath, 0, uint64(exec.pos.TXID), uint64(exec.pos.TXID), fmt.Errorf(\"%w: %w\", ErrLTXCorrupted, err))\n\t\treturn info, ltxErr\n\t}\n\tinfo.offset = dec.Header().WALOffset + dec.Header().WALSize\n\tinfo.salt1 = dec.Header().WALSalt1\n\tinfo.salt2 = dec.Header().WALSalt2\n\tinfo.prevCommit = dec.Header().Commit\n\n\t// If LTX WAL offset is larger than real WAL then the WAL has been truncated.\n\tif fi, err := os.Stat(db.WALPath()); err != nil {\n\t\treturn info, fmt.Errorf(\"open wal file: %w\", err)\n\t} else if info.offset > fi.Size() {\n\t\texec.state.truncatePassiveFailed = false\n\n\t\t// If we previously synced to the exact end of the WAL, this truncation\n\t\t// is expected (normal checkpoint behavior). Reset position and continue\n\t\t// incrementally rather than triggering a full snapshot. See issue #927.\n\t\tif exec.state.syncedToWALEnd {\n\t\t\t// Read new WAL header to get current salt values\n\t\t\thdr, err := readWALHeader(db.WALPath())\n\t\t\tif err != nil {\n\t\t\t\treturn info, fmt.Errorf(\"read wal header after expected truncation: %w\", err)\n\t\t\t}\n\n\t\t\tinfo.offset = WALHeaderSize\n\t\t\tinfo.salt1 = binary.BigEndian.Uint32(hdr[16:])\n\t\t\tinfo.salt2 = binary.BigEndian.Uint32(hdr[20:])\n\t\t\tinfo.snapshotting = false\n\t\t\tinfo.reason = \"\"","sourceCodeStart":1694,"sourceCodeEnd":1730,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/db.go#L1694-L1730","documentation":"os.Stat on the database's live WAL file failed while verify was checking whether the LTX WAL offset exceeds the real WAL size. The sync cannot compare LTX state against the WAL if the WAL file cannot be stat'ed.","triggerScenarios":"During verifyWithExecutor, os.Stat(db.WALPath()) returns an error — WAL file deleted between checkpoint and verify, or permission/IO error on the path.","commonSituations":"Another process deleted or moved the -wal file; database replaced on disk while litestream holds the old path; directory permissions changed; NFS/network filesystem glitches.","solutions":["Confirm the -wal file exists next to the database and the path in config matches the actual DB path","Check filesystem permissions for the litestream user","Fix the wrapped inner error (path, permissions, mount) and let the next sync retry","If the database was replaced, restart litestream and reset local state"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fi, err := os.Stat(dbPath + \"-wal\")\nif err != nil { /* refuse to sync: WAL missing or unreadable */ }","typeGuard":"func walExists(dbPath string) bool {\n    _, err := os.Stat(dbPath + \"-wal\")\n    return err == nil\n}","tryCatchPattern":"if err := db.Sync(ctx); err != nil {\n    if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) {\n        // fix path/permissions, then retry sync\n    }\n}","preventionTips":["Verify the config path points at the live database (with -wal present)","Run litestream under a user with read access to the DB directory","Avoid external jobs that move/delete WAL files"],"tags":["wal","stat","filesystem","go"],"backgroundTag":"file-not-found","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"}