FiloSottile/age · error

failed to parse header: %w

Error message

failed to parse header: %w

What it means

format.Parse reads each recipient stanza with ReadStanza; any failure there (I/O error, malformed body line, too-long line) is wrapped as 'failed to parse header'. The inner error carries the precise cause (see ParseError.Unwrap).

Source

Thrown at internal/format/format.go:358

			}

			prefix, args := splitArgs(line)
			if prefix != string(footerPrefix) || len(args) != 1 {
				return nil, nil, errorf("malformed closing line: %q", line)
			}
			h.MAC, err = DecodeString(args[0])
			if err != nil || len(h.MAC) != 32 {
				return nil, nil, errorf("malformed closing line %q: %v", line, err)
			}
			break
		}
		if len(h.Recipients) == maxRecipientStanzas {
			return nil, nil, errorf("header contains more than %d recipient stanzas", maxRecipientStanzas)
		}

		s, err := sr.ReadStanza()
		if err != nil {
			return nil, nil, fmt.Errorf("failed to parse header: %w", err)
		}
		h.Recipients = append(h.Recipients, s)
	}
	if len(h.Recipients) == 0 {
		return nil, nil, errorf("no recipient stanzas")
	}

	// If input is a bufio.Reader, rr might be equal to input because
	// bufio.NewReader short-circuits. In this case we can just return it (and
	// we would end up reading the buffer twice if we prepended the peek below).
	if rr == input {
		return h, rr, nil
	}
	// Otherwise, unwind the bufio overread and return the unbuffered input.
	buf, err := rr.Peek(rr.Buffered())
	if err != nil {
		return nil, nil, errorf("internal error: %v", err)
	}

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Unwrap with errors.Is/As to find the root cause (EOF vs malformed line)
  2. Re-transfer or re-encrypt the file; a broken stanza cannot be repaired
  3. Check for legacy beta formats and decrypt with rage / age v1.0.0-beta6 if applicable
  4. Limit stanza counts if generating headers programmatically (max 16)

Example fix

// before
if err != nil { log.Fatal(err) } // failed to parse header: malformed body line ...
// after
var pe *age.ParseError
if errors.As(err, &pe) { log.Fatalf("bad header at cause: %v", pe.Unwrap()) }
Defensive patterns

Strategy: try-catch

Validate before calling

// sanity-check structure before deep parsing
head, _ := bufio.NewReader(f).Peek(128)
if !bytes.Contains(head, []byte("-> ")) && !bytes.Contains(head, []byte("---")) { return errors.New("header missing recipient stanzas") }

Type guard

var pe *age.ParseError
if errors.As(err, &pe) { cause := pe.Unwrap() /* io.EOF vs malformed line */ }

Try / catch

hdr, _, err := format.Parse(r)
if err != nil {
    var pe *age.ParseError
    if errors.As(err, &pe) { return fmt.Errorf("bad stanza: %w", pe.Unwrap()) }
    return err
}

Prevention

When it happens

Trigger: format.Parse calls sr.ReadStanza() while collecting recipient stanzas and receives an error — invalid base64 body, oversized line, or EOF mid-stanza (see errors 60–61).

Common situations: Corrupted .age files from bad transfers; old beta-format files; concatenated or hand-modified headers; more than 16 stanzas also rejected (distinct message) but often from automation loops appending stanzas.

Understand the failure class

Related errors


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