{"record":{"id":"ef744fe26d75dd14","repo":"benbjohnson/litestream","slug":"invalid-v0-3-x-snapshot-filename-q","errorCode":null,"errorMessage":"invalid v0.3.x snapshot filename: %q","messagePattern":"invalid v0\\.3\\.x snapshot filename: %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3.go","lineNumber":120,"sourceCode":"\n// FormatWALSegmentFilenameV3 returns the filename for a v0.3.x WAL segment.\n// Format: {index:08x}_{offset:08x}.wal.lz4\nfunc FormatWALSegmentFilenameV3(index int, offset int64) string {\n\treturn fmt.Sprintf(\"%08x_%08x.wal.lz4\", index, offset)\n}\n\nvar (\n\tsnapshotRegexV3   = regexp.MustCompile(`^([0-9a-f]{8})\\.snapshot\\.lz4$`)\n\twalSegmentRegexV3 = regexp.MustCompile(`^([0-9a-f]{8})_([0-9a-f]{8,16})\\.wal\\.lz4$`)\n\tgenerationRegexV3 = regexp.MustCompile(`^[0-9a-f]{16}$`)\n)\n\n// ParseSnapshotFilenameV3 parses a v0.3.x snapshot filename and returns the index.\n// Returns an error if the filename does not match the expected format.\nfunc ParseSnapshotFilenameV3(filename string) (index int, err error) {\n\tm := snapshotRegexV3.FindStringSubmatch(filename)\n\tif m == nil {\n\t\treturn 0, fmt.Errorf(\"invalid v0.3.x snapshot filename: %q\", filename)\n\t}\n\tidx, err := strconv.ParseInt(m[1], 16, 32)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"invalid snapshot path: %s: %w\", filename, err)\n\t}\n\treturn int(idx), nil\n}\n\n// ParseWALSegmentFilenameV3 parses a v0.3.x WAL segment filename.\n// Returns the WAL index and byte offset, or an error if the filename is invalid.\nfunc ParseWALSegmentFilenameV3(filename string) (index int, offset int64, err error) {\n\tm := walSegmentRegexV3.FindStringSubmatch(filename)\n\tif m == nil {\n\t\treturn 0, 0, fmt.Errorf(\"invalid v0.3.x WAL segment filename: %q\", filename)\n\t}\n\tidx, err := strconv.ParseInt(m[1], 16, 32)\n\tif err != nil {\n\t\treturn 0, 0, fmt.Errorf(\"invalid wal segment path: %s: %w\", filename, err)","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/v3.go#L102-L138","documentation":"ParseSnapshotFilenameV3 extracts the integer index from a v0.3.x snapshot filename, which must match the format '{index:08x}.snapshot.lz4' (e.g. '00000000.snapshot.lz4'). This error is returned when the filename does not match the snapshotRegexV3 pattern at all, meaning the file is not recognized as a v0.3.x snapshot. Litestream throws it to defend against parsing garbage or foreign files found while scanning a v0.3.x replica layout.","triggerScenarios":"Calling ParseSnapshotFilenameV3 with a filename that doesn't match the v0.3.x format: a v0.3.x-era name variant, an LTX-era name like '0000000000000000-0000000000000001.ltx', a path with directories included instead of the bare filename, an uncompressed '.snapshot' (missing .lz4), or a typo'd/corrupted name.","commonSituations":"Migrating from Litestream v0.3.x to newer versions and scanning old replica buckets manually; custom tooling that lists replica objects and parses names; passing a full object key/path instead of the base filename; extra files (checkpoints, manifests) dropped into the snapshots directory.","solutions":["Pass only the base filename, not the full path: use filepath.Base(path) before calling ParseSnapshotFilenameV3.","Verify the filename matches the v0.3.x format '{index:08x}.snapshot.lz4'; use IsGenerationIDV3/regex or skip non-matching files when scanning a directory.","If the file is an LTX file from a newer Litestream, do not use the V3 parser; use the current LTX parsing APIs instead.","Check that the file is a genuine v0.3.x snapshot and not a manifest or unrelated file that landed in the snapshots/ directory."],"exampleFix":"// before\nidx, err := litestream.ParseSnapshotFilenameV3(objKey) // objKey = \"generations/abcdef0123456789/snapshots/00000000.snapshot.lz4\"\n\n// after\nidx, err := litestream.ParseSnapshotFilenameV3(filepath.Base(objKey))","handlingStrategy":"validation","validationCode":"var snapshotReV3 = regexp.MustCompile(`^[0-9a-f]{8}\\.snapshot\\.lz4$`)\nfunc isValidSnapshotFilenameV3(name string) bool { return snapshotReV3.MatchString(filepath.Base(name)) }","typeGuard":"func isV3SnapshotName(s string) bool { return snapshotReV3.MatchString(filepath.Base(s)) }\nif isV3SnapshotName(key) { idx, err := litestream.ParseSnapshotFilenameV3(filepath.Base(key)) }","tryCatchPattern":"idx, err := litestream.ParseSnapshotFilenameV3(filepath.Base(name))\nif err != nil {\n    log.Printf(\"skipping non-v0.3.x snapshot %q: %v\", name, err)\n    return skipFile\n}","preventionTips":["Always pass filepath.Base(name), never a full object key or path","Pre-validate names with a regex identical to ^[0-9a-f]{8}\\.snapshot\\.lz4$ before parsing","When listing replica objects, filter to the snapshots/ directory and skip known non-snapshot files","Never mix LTX-era filenames into V3 parsing code paths"],"tags":["filename-parsing","v0-3-migration","validation"],"backgroundTag":"invalid-argument-format","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"}