{"record":{"id":"99cdb396e9f66943","repo":"juicedata/juicefs","slug":"failed-to-read-footer-w","errorCode":null,"errorMessage":"failed to read footer: %w","messagePattern":"failed to read footer: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/load.go","lineNumber":283,"sourceCode":"\tdefer fp.Close()\n\n\tif !ctx.IsSet(\"offset\") {\n\t\treturn showBakSummary(ctx, fp, false)\n\t}\n\n\toffset := ctx.Int64(\"offset\")\n\tif offset == -1 {\n\t\treturn showBakSummary(ctx, fp, true)\n\t}\n\n\treturn showBakDetail(ctx, fp, offset)\n}\n\nfunc showBakSummary(ctx *cli.Context, fp *os.File, withOffset bool) error {\n\tbak := &meta.BakFormat{}\n\tfooter, err := bak.ReadFooter(fp)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read footer: %w\", err)\n\t}\n\n\tfmt.Printf(\"Backup Version: %d\\n\", footer.Msg.Version)\n\tdata := make([][]string, 0, len(footer.Msg.Infos))\n\tfor name, info := range footer.Msg.Infos {\n\t\tif withOffset {\n\t\t\tdata = append(data, []string{name, fmt.Sprintf(\"%d\", info.Num), fmt.Sprintf(\"%d\", info.Offset)})\n\t\t} else {\n\t\t\tdata = append(data, []string{name, fmt.Sprintf(\"%d\", info.Num)})\n\t\t}\n\t}\n\tsort.Slice(data, func(i, j int) bool {\n\t\treturn data[i][0] < data[j][0]\n\t})\n\n\tif withOffset {\n\t\tfmt.Println(strings.Repeat(\"-\", 34))\n\t\tfmt.Printf(\"%-10s| %-10s| %-10s\\n\", \"Name\", \"Num\", \"Offset\")","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/cmd/load.go#L265-L301","documentation":"`showBakSummary` reads the backup file's footer via meta.BakFormat.ReadFooter(fp). The footer is a length-prefixed protobuf record at the end of the file containing the backup version and per-segment info. If the footer cannot be read or decoded (corrupt, truncated, or not a JuiceFS backup file), the error is wrapped as `failed to read footer: <error>`.","triggerScenarios":"Inspecting (`statBak`/summary) a file that is not a JuiceFS binary backup (e.g. a plain JSON dump, or a text file), or a backup that was truncated by an interrupted dump/transfer, or one whose trailing bytes were cut off.","commonSituations":"Downloading the backup from object storage incompletely; opening a gzipped backup without decompressing; a backup written by a much newer JuiceFS client with an incompatible footer version; corrupted file from a failed rsync.","solutions":["Verify the file is a complete JuiceFS binary backup (check size against the source, re-download if needed)","Confirm you are not passing a plain JSON dump — footers only exist in binary backups","Decompress first if the backup is gzipped","Re-create the backup with `juicefs dump --backup` and retry; if the version is newer, upgrade your JuiceFS client"],"exampleFix":"// before\njuicefs load --stat meta.sqlite3 backup.json.gz      # still gzipped\n// after\ngunzip backup.json.gz && juicefs load --stat meta.sqlite3 backup.json","handlingStrategy":"validation","validationCode":"fi, err := os.Stat(backupPath)\nif err != nil { return err }\nif fi.Size() < 64 { return fmt.Errorf(\"file too small to be a backup: %s\", backupPath) }\nif hasGzipMagic(backupPath) { return fmt.Errorf(\"decompress before inspecting\") }","typeGuard":null,"tryCatchPattern":"if err := showSummary(fp); err != nil {\n    if strings.Contains(err.Error(), \"failed to read footer\") {\n        log.Fatalf(\"truncated/corrupt or non-binary backup: %v\", err)\n    }\n}","preventionTips":["Verify backup size/checksum after every dump or download","Only feed binary backups to footer-parsing commands, not JSON dumps","Decompress gzipped backups before inspection","Keep client versions compatible with the backup writer version"],"tags":["backup-restore","protobuf","corruption"],"backgroundTag":"protobuf-unmarshal-failed","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}