FiloSottile/age · error · ParseError
parsing age header:
Error message
parsing age header:
What it means
errorf constructs a *age.ParseError wrapping header-parsing failures. Callers of format.Parse prepend 'parsing age header: ', so any structural header problem (bad intro line, malformed args, bad closing line) surfaces under this prefix. Check with errors.As against *age.ParseError.
Source
Thrown at internal/format/format.go:279
}
func (r *headerReader) Peek(n int) ([]byte, error) {
if r.n+n > maxHeaderBytes {
return nil, errorf("header exceeds 2 MiB")
}
return r.r.Peek(n)
}
func (e *ParseError) Error() string {
return "parsing age header: " + e.err.Error()
}
func (e *ParseError) Unwrap() error {
return e.err
}
func errorf(format string, a ...any) error {
return &ParseError{fmt.Errorf(format, a...)}
}
// describeIntro returns a quoted description of a bad intro line, or an empty
// string if the line contains recognizable private key material.
func describeIntro(line string) string {
for _, prefix := range []string{"AGE-SECRET-KEY-", "AGE-PLUGIN-"} {
if strings.HasPrefix(line, prefix) {
return ""
}
}
// Preserve enough context to diagnose a mangled intro without echoing an
// arbitrarily long first line.
return fmt.Sprintf("%q", line[:min(len(line), len(intro))])
}
// Parse returns the header and a Reader that begins at the start of the
// payload.
func Parse(input io.Reader) (*Header, io.Reader, error) {View on GitHub (pinned to b74dce4cdb)
Solutions
- Confirm the input actually is an age file (starts with 'age-encryption.org/v1')
- If the file is PEM-armored, wrap the reader with armor.NewReader before parsing
- Re-transfer the file if bytes at the start were altered or corrupted
- Use errors.As(err, *age.ParseError) to get structured detail about which line failed
Example fix
// before
out, err := age.Decrypt(f, ids) // parsing age header: ...
// after
br := bufio.NewReader(f)
head, _ := br.Peek(4)
var r io.Reader = br
if bytes.Equal(head, []byte("-----")) { r = armor.NewReader(br) }
out, err := age.Decrypt(r, ids) Defensive patterns
Strategy: try-catch
Validate before calling
head, _ := bufio.NewReader(f).Peek(len("age-encryption.org/v1\n"))
if !bytes.HasPrefix(head, []byte("age-encryption.org/v1")) { return errors.New("not an age file") } Type guard
var pe *age.ParseError
if errors.As(err, &pe) { /* structured header parse failure */ } Try / catch
out, err := age.Decrypt(r, ids)
if err != nil {
var pe *age.ParseError
if errors.As(err, &pe) { return fmt.Errorf("invalid age header: %w", pe.Unwrap()) }
return err
} Prevention
- Peek the intro line before parsing to reject non-age input
- Decode armored files with armor.NewReader before Decrypt
- Keep the decrypting tool version compatible with the encrypting one
When it happens
Trigger: format.Parse encounters a header that violates the format — wrong intro line ('age-encryption.org/v1' missing), invalid argument counts, malformed closing line — and wraps it via errorf before callers add the 'parsing age header: ' prefix.
Common situations: Pointing age at a plaintext or non-age file; decrypting armored files without armor mode; files produced by incompatible tools; corruption in the first bytes of the file.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- malformed body line %q: stanza ended without a short line no
- failed to parse header: %w
- 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/23f9e9815cca243c.
Report an issue: GitHub.