FiloSottile/age · error

failed to read rest of file: %w

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.

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)

Solutions

  1. Inspect a local, fully written copy of the file instead of a remote/streaming source
  2. Verify storage health / network stability for the source path
  3. Retry the inspection; transient read failures won't corrupt anything
  4. 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

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


AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31). Data as JSON: /api/errors/9f3de315f68ca792. Report an issue: GitHub.