FiloSottile/age · error
failed to read rest of file
Error message
failed to read rest of file: %w
What it means
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.
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
Example fix
// before
data, err := inspect.Inspect(remoteReader, -1) // failed to read rest of file
// after
local, err := os.CreateTemp("", "age"); io.Copy(local, remoteReader); local.Seek(0, 0)
st, _ := local.Stat()
data, err := inspect.Inspect(local, st.Size()) Defensive patterns
Strategy: try-catch
Validate before calling
st, err := os.Stat(path)
if err != nil { return err }
if st.Size() <= 0 { return errors.New("empty file") } Type guard
var pe *age.ParseError
if errors.As(err, &pe) { /* Inspect-level wrapped error */ } Try / catch
data, err := inspect.Inspect(r, -1)
if err != nil {
if errors.Is(err, io.ErrUnexpectedEOF) { return fmt.Errorf("stream cut off during inspection: %w", err) }
return err
} Prevention
- 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
When it happens
Trigger: inspect.Inspect runs io.Copy over the post-header remainder and the underlying reader (file, pipe, network) returns an I/O error mid-stream.
Common situations: Inspecting files over unstable mounts/network shares; reading from pipes or sockets that close early; dying disks; armored input truncated mid-body.
Related errors
- encrypted size too small
- failed to compute stream overhead
- failed to re-serialize header
- failed to read chunk at offset
- failed to read final chunk
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/9f3de315f68ca792.
Report an issue: GitHub.
Appendix: source
Thrown at internal/inspect/inspect.go:81
switch s.Type {
case "X25519", "ssh-rsa", "ssh-ed25519", "p256tag", "piv-p256":
data.Postquantum = "no"
case "mlkem768x25519", "scrypt", "mlkem768p256tag":
// Keep "yes".
default:
if data.Postquantum != "no" {
data.Postquantum = "unknown"
}
}
}
// If fileSize is not provided, or if it's the size of the armored file
// (which can have LF or CRLF line endings, varying its size), read to
// the end to determine it.
if fileSize == -1 || data.Armor {
n, err := io.Copy(io.Discard, rest)
if err != nil {
return nil, fmt.Errorf("failed to read rest of file: %w", err)
}
fileSize = data.Sizes.Header + n
if !tr.done {
panic("trackReader not done after io.Copy")
}
if tr.count != fileSize && !data.Armor {
panic("trackReader count mismatch")
}
data.Sizes.Armor = tr.count - fileSize
}
data.Sizes.Overhead, err = streamOverhead(fileSize - data.Sizes.Header)
if err != nil {
return nil, fmt.Errorf("failed to compute stream overhead: %w", err)
}
data.Sizes.MinPayload = fileSize - data.Sizes.Header - data.Sizes.Overhead
data.Sizes.MaxPayload = data.Sizes.MinPayload
return data, nil
}View on GitHub (pinned to b74dce4cdb)