{"record":{"id":"2cda2dd7032e9e3f","repo":"benbjohnson/litestream","slug":"prev-wal-offset-is-less-than-the-header-size-d","errorCode":null,"errorMessage":"prev WAL offset is less than the header size: %d","messagePattern":"prev WAL offset is less than the header size: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"db.go","lineNumber":1784,"sourceCode":"\t\t}\n\t\tinfo.reason = \"wal header salt reset, snapshotting\"\n\t\treturn info, nil\n\t}\n\n\t// If offset is at the beginning of the first page, we can't check for previous page.\n\tprevWALOffset := info.offset - frameSize\n\tdb.Logger.Debug(\"verify\", \"saltMatch\", saltMatch, \"prevWALOffset\", prevWALOffset)\n\n\tif prevWALOffset == WALHeaderSize {\n\t\tif saltMatch { // No writes occurred since last sync, salt still matches\n\t\t\tinfo.snapshotting = false\n\t\t\treturn info, nil\n\t\t}\n\t\t// Salt has changed but we don't know if writes occurred since last sync\n\t\tinfo.reason = \"wal header salt reset, snapshotting\"\n\t\treturn info, nil\n\t} else if prevWALOffset < WALHeaderSize {\n\t\treturn info, fmt.Errorf(\"prev WAL offset is less than the header size: %d\", prevWALOffset)\n\t}\n\n\t// If we can't verify the last page is in the last LTX file, then we need to snapshot.\n\tlastPageMatch, err := db.lastPageMatch(ctx, dec, prevWALOffset, frameSize)\n\tif err != nil {\n\t\treturn info, fmt.Errorf(\"last page match: %w\", err)\n\t} else if !lastPageMatch {\n\t\tinfo.reason = \"last page does not exist in last ltx file, wal overwritten by another process\"\n\t\treturn info, nil\n\t}\n\n\tdb.Logger.Debug(\"verify.2\", \"lastPageMatch\", lastPageMatch)\n\n\t// Salt has changed which could indicate a FULL checkpoint.\n\t// If we have a last page match, then we can assume that the WAL has not been overwritten.\n\tif !saltMatch {\n\t\tdb.Logger.Log(ctx, internal.LevelTrace, \"wal restarted\",\n\t\t\t\"salt1\", salt1,","sourceCodeStart":1766,"sourceCodeEnd":1802,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/db.go#L1766-L1802","documentation":"The previously synced WAL offset stored in sync state is smaller than the 32-byte WAL header, which is structurally impossible for a valid WAL position. verify rejects it rather than reading a frame at an invalid offset.","triggerScenarios":"prevWALOffset (from the last sync's recorded state) is > 0 but < WALHeaderSize — corrupted or hand-edited sync state, or a zero/truncated LTX-derived offset fed into verify.","commonSituations":"Manually editing or partially restoring LTX/replica state; corruption of local offset bookkeeping; restoring an LTX file from an incompatible litestream version.","solutions":["Run `litestream reset <db>` to discard local state and resnapshot from the replica","Inspect the LTX file's WALHeader.WALOffset for the offending value (`litestream ltx`)","Avoid manually editing or truncating LTX files; always restore via litestream"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// sanity-check any restored LTX state\nif hdr.WALOffset != 0 && hdr.WALOffset < 32 { /* invalid state: reset */ }","typeGuard":"func validWALOffset(off int64) bool { return off == 0 || off >= 32 }","tryCatchPattern":"if err := db.Sync(ctx); err != nil {\n    if strings.Contains(err.Error(), \"less than the header size\") {\n        // local state invalid: litestream reset and resnapshot\n    }\n}","preventionTips":["Never hand-edit LTX files or offset state","Always restore state via litestream tooling","Run `litestream ltx` to validate files after manual recovery attempts"],"tags":["wal","offset","invariant","ltx"],"backgroundTag":"internal-invariant-violation","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"}