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

  1. Confirm the input actually is an age file (starts with 'age-encryption.org/v1')
  2. If the file is PEM-armored, wrap the reader with armor.NewReader before parsing
  3. Re-transfer the file if bytes at the start were altered or corrupted
  4. 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

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


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