{"record":{"id":"9f3de315f68ca792","repo":"FiloSottile/age","slug":"failed-to-read-rest-of-file-w","errorCode":null,"errorMessage":"failed to read rest of file: %w","messagePattern":"failed to read rest of file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/inspect/inspect.go","lineNumber":81,"sourceCode":"\t\tswitch s.Type {\n\t\tcase \"X25519\", \"ssh-rsa\", \"ssh-ed25519\", \"p256tag\", \"piv-p256\":\n\t\t\tdata.Postquantum = \"no\"\n\t\tcase \"mlkem768x25519\", \"scrypt\", \"mlkem768p256tag\":\n\t\t\t// Keep \"yes\".\n\t\tdefault:\n\t\t\tif data.Postquantum != \"no\" {\n\t\t\t\tdata.Postquantum = \"unknown\"\n\t\t\t}\n\t\t}\n\t}\n\n\t// If fileSize is not provided, or if it's the size of the armored file\n\t// (which can have LF or CRLF line endings, varying its size), read to\n\t// the end to determine it.\n\tif fileSize == -1 || data.Armor {\n\t\tn, err := io.Copy(io.Discard, rest)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read rest of file: %w\", err)\n\t\t}\n\t\tfileSize = data.Sizes.Header + n\n\t\tif !tr.done {\n\t\t\tpanic(\"trackReader not done after io.Copy\")\n\t\t}\n\t\tif tr.count != fileSize && !data.Armor {\n\t\t\tpanic(\"trackReader count mismatch\")\n\t\t}\n\t\tdata.Sizes.Armor = tr.count - fileSize\n\t}\n\tdata.Sizes.Overhead, err = streamOverhead(fileSize - data.Sizes.Header)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to compute stream overhead: %w\", err)\n\t}\n\tdata.Sizes.MinPayload = fileSize - data.Sizes.Header - data.Sizes.Overhead\n\tdata.Sizes.MaxPayload = data.Sizes.MinPayload\n\treturn data, nil\n}","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/internal/inspect/inspect.go#L63-L99","documentation":"When the caller doesn't provide a file size (fileSize == -1) or the file is armored, Inspect drains the remaining bytes with io.Copy(io.Discard, rest) to compute the payload size. Any read error from the underlying reader is wrapped with this message.","triggerScenarios":"inspect.Inspect runs io.Copy over the post-header remainder and the underlying reader (file, pipe, network) returns an I/O error mid-stream.","commonSituations":"Inspecting files over unstable mounts/network shares; reading from pipes or sockets that close early; dying disks; armored input truncated mid-body.","solutions":["Inspect a local, fully written copy of the file instead of a remote/streaming source","Verify storage health / network stability for the source path","Retry the inspection; transient read failures won't corrupt anything","Provide an accurate fileSize to skip the drain path where applicable"],"exampleFix":"// before\ndata, err := inspect.Inspect(remoteReader, -1) // failed to read rest of file\n// after\nlocal, err := os.CreateTemp(\"\", \"age\"); io.Copy(local, remoteReader); local.Seek(0, 0)\nst, _ := local.Stat()\ndata, err := inspect.Inspect(local, st.Size())","handlingStrategy":"try-catch","validationCode":"st, err := os.Stat(path)\nif err != nil { return err }\nif st.Size() <= 0 { return errors.New(\"empty file\") }","typeGuard":"var pe *age.ParseError\nif errors.As(err, &pe) { /* Inspect-level wrapped error */ }","tryCatchPattern":"data, err := inspect.Inspect(r, -1)\nif err != nil {\n    if errors.Is(err, io.ErrUnexpectedEOF) { return fmt.Errorf(\"stream cut off during inspection: %w\", err) }\n    return err\n}","preventionTips":["Copy remote/streamed files to local storage before inspecting","Retry inspection on transient I/O failures","Check mount/disk health if failures repeat on the same source"],"tags":["io","inspect","stream-read"],"backgroundTag":"io-read-failure","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}