{"record":{"id":"af965a49ab39bd3c","repo":"benbjohnson/litestream","slug":"verify","errorCode":null,"errorMessage":"verify","messagePattern":"verify","errorType":"exception","errorClass":"ErrLTXCorrupted","httpStatus":null,"severity":"critical","filePath":"db.go","lineNumber":634,"sourceCode":"\t}\n\n\tminTXID, maxTXID, err := db.MaxLTX()\n\tif err != nil {\n\t\treturn ltx.Pos{}, err\n\t} else if minTXID == 0 {\n\t\treturn ltx.Pos{}, nil // no replication yet\n\t}\n\n\tltxPath := db.LTXPath(0, minTXID, maxTXID)\n\tf, err := os.Open(ltxPath)\n\tif err != nil {\n\t\treturn ltx.Pos{}, NewLTXError(\"open\", ltxPath, 0, uint64(minTXID), uint64(maxTXID), err)\n\t}\n\tdefer func() { _ = f.Close() }()\n\n\tdec := ltx.NewDecoder(f)\n\tif err := dec.Verify(); err != nil {\n\t\treturn ltx.Pos{}, NewLTXError(\"verify\", ltxPath, 0, uint64(minTXID), uint64(maxTXID), fmt.Errorf(\"%w: %w\", ErrLTXCorrupted, err))\n\t}\n\n\tpos := dec.PostApplyPos()\n\tdb.pos.value = &pos\n\n\treturn pos, nil\n}\n\n// invalidatePosCache clears the cached position so the next call to Pos()\n// recomputes it from disk. Call this when L0 LTX files are deleted or\n// when the L0 directory is cleared.\nfunc (db *DB) invalidatePosCache() {\n\tdb.pos.Lock()\n\tdb.pos.value = nil\n\tdb.pos.Unlock()\n}\n\n// Notify returns a channel that closes when the shadow WAL changes.","sourceCodeStart":616,"sourceCodeEnd":652,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/db.go#L616-L652","documentation":"During restore/apply, each LTX file is verified with the LTX decoder before applying. If dec.Verify() fails, the error is wrapped by NewLTXError with op \"verify\" and tagged ErrLTXCorrupted. It means the local LTX file failed checksum/structural verification and cannot be trusted.","triggerScenarios":"Applying or restoring from an LTX file whose bytes were corrupted — truncated download, bit rot on disk, partial write during crash, or tampering; dec.Verify() returns non-nil and db.go wraps it as op=verify with minTXID/maxTXID context.","commonSituations":"Disk full during replication producing truncated files; faulty storage medium; interrupted network copy of the database directory; manually copied/partially synced LTX directories between hosts.","solutions":["Run litestream reset on the database to clear corrupted local LTX state, then re-replicate","Verify remote replicas still hold intact files and restore from a replica instead of local cache","Check host disk health (SMART) and free space; fix the corruption source","If remote files are also corrupt, restore from the latest valid backup point before the corruption","Enable auto-recover replica option to automatically reset local state on LTX errors"],"exampleFix":"// before: restore keeps failing on corrupt local state\n$ litestream restore -o db.sqlite /path/to/db\n// verify: ltx corrupted\n// after: clear local state and retry\n$ litestream reset /path/to/db\n$ litestream restore -o db.sqlite /path/to/db","handlingStrategy":"fallback","validationCode":"// Verify LTX integrity outside the apply path before restore\ndec := ltx.NewDecoder(f)\nif err := dec.Verify(); err != nil {\n    return fmt.Errorf(\"%w: %w\", ErrLTXCorrupted, err)\n}","typeGuard":null,"tryCatchPattern":"pos, err := db.restoreApplyLTX(ltxPath, minTXID, maxTXID)\nif errors.Is(err, ErrLTXCorrupted) {\n    // fall back: litestream reset + restore from remote replica\n}","preventionTips":["Monitor disk health and free space on the replication host","Never manually copy or rsync LTX directories mid-write","Enable the auto-recover replica option for automatic reset on corruption","Keep at least one healthy remote replica for fallback restore"],"tags":["corruption","checksum","restore","ltx"],"backgroundTag":"checksum-mismatch","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"}