FiloSottile/age · error

failed to read header: %w

Error message

failed to read header: %w

What it means

internal/inspect.Inspect parses the age header via format.Parse and wraps any parse failure as 'failed to read header'. Since Inspect reports metadata about a file, this means the input is not a well-formed age file or is truncated in the header.

Source

Thrown at internal/inspect/inspect.go:52

	data := &Metadata{
		Version:     "age-encryption.org/v1",
		Postquantum: "yes",
	}

	tr := &trackReader{r: r}
	br := bufio.NewReader(tr)
	const maxWhitespace = 1024
	start, _ := br.Peek(maxWhitespace + len(armor.Header))
	if strings.HasPrefix(string(bytes.TrimSpace(start)), armor.Header) {
		r = armor.NewReader(br)
		data.Armor = true
	} else {
		r = br
	}

	hdr, rest, err := format.Parse(r)
	if err != nil {
		return nil, fmt.Errorf("failed to read header: %w", err)
	}

	buf := &bytes.Buffer{}
	if err := hdr.Marshal(buf); err != nil {
		return nil, fmt.Errorf("failed to re-serialize header: %w", err)
	}
	data.Sizes.Header = int64(buf.Len())

	for _, s := range hdr.Recipients {
		data.StanzaTypes = append(data.StanzaTypes, s.Type)
		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"

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Confirm the target file is age-encrypted and complete before inspecting
  2. If PEM-armored, inspect the decoded stream or ensure armor handling is used
  3. Print the wrapped cause (errors.As *age.ParseError) to identify the offending line
  4. Re-download the file if the size looks short

Example fix

// before
data, err := inspect.Inspect(f, size) // failed to read header
// after
head, _ := bufio.NewReader(f).Peek(len("age-encryption.org"))
if !bytes.HasPrefix(head, []byte("age-encryption")) { return fmt.Errorf("not an age file") }
f.Seek(0, 0)
data, err := inspect.Inspect(f, size)
Defensive patterns

Strategy: try-catch

Validate before calling

head, _ := bufio.NewReader(f).Peek(len("age-encryption.org/v1"))
if !bytes.HasPrefix(head, []byte("age-encryption.org/v1")) { return errors.New("age-inspect: not an age file") }

Type guard

var pe *age.ParseError
if errors.As(err, &pe) { /* header-level failure from format.Parse */ }

Try / catch

data, err := inspect.Inspect(f, size)
if err != nil {
    var pe *age.ParseError
    if errors.As(err, &pe) { return fmt.Errorf("cannot inspect: %w", pe.Unwrap()) }
    return err
}

Prevention

When it happens

Trigger: inspect.Inspect (called by the age-inspect command's main, or tests) calls format.Parse(r) and err != nil — non-age input, corrupted or truncated header.

Common situations: Running age-inspect on plaintext files, wrong extensions, partially uploaded files, or armored files passed without armor handling.

Related errors


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