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
- Unwrap with errors.Is/As to find the root cause (EOF vs malformed line)
- Re-transfer or re-encrypt the file; a broken stanza cannot be repaired
- Check for legacy beta formats and decrypt with rage / age v1.0.0-beta6 if applicable
- 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
- Re-transfer corrupted files instead of patching them
- Cap recipient stanza counts (max 16) in generators
- Decrypt legacy beta files with rage / age v1.0.0-beta6
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- malformed body line %q: stanza ended without a short line no
- parsing age header:
- unexpected newline character
- invalid stanza type: %q
- invalid stanza argument: %q
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/f6de92e026407f59.
Report an issue: GitHub.