FiloSottile/age · error
failed to read header: %w
Error message
failed to read header: %w
What it means
format.Parse reads the header via a bufio reader; if ReadBytes fails while reading the closing '---' footer line (EOF before newline or an I/O error), it returns this wrapped error. It means the file ended before the header's closing line was seen.
Source
Thrown at internal/format/format.go:339
}
return nil, nil, errorf("unexpected intro, expected %q", intro)
}
sr := &StanzaReader{r: hr}
for {
peek, err := hr.Peek(len(footerPrefix))
if err != nil {
// headerReader errors are already ParseErrors; don't nest the prefix.
if _, ok := err.(*ParseError); ok {
return nil, nil, err
}
return nil, nil, errorf("failed to read header: %w", err)
}
if bytes.Equal(peek, footerPrefix) {
line, err := hr.ReadBytes('\n')
if err != nil {
return nil, nil, fmt.Errorf("failed to read header: %w", err)
}
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 {View on GitHub (pinned to b74dce4cdb)
Solutions
- Re-obtain a complete copy of the encrypted file and verify its size/hash
- Ensure the producer finished flushing/closing before the consumer parses
- Distinguish truncation from transport failure with errors.Is(err, io.ErrUnexpectedEOF) / io.EOF
- If the file is legitimately tiny, check it was actually written by age and not an empty/garbage file
Example fix
// before
hdr, _, err := format.Parse(f) // failed to read header: unexpected EOF
// after
st, _ := f.Stat()
if st.Size() < 100 { return fmt.Errorf("refusing parse: file is %d bytes", st.Size()) }
hdr, _, err := format.Parse(f) Defensive patterns
Strategy: try-catch
Validate before calling
st, err := os.Stat(path)
if err != nil { return err }
if st.Size() < 100 { return fmt.Errorf("file too small to contain a full age header") } Type guard
var pe *age.ParseError
if errors.As(err, &pe) && errors.Is(pe.Unwrap(), io.ErrUnexpectedEOF) { /* header cut before closing line */ } Try / catch
hdr, body, err := format.Parse(r)
if err != nil {
if errors.Is(err, io.ErrUnexpectedEOF) { return fmt.Errorf("file truncated in header: %w", err) }
return err
} Prevention
- Confirm complete transfer (size/hash) before parsing
- Flush and close writers before consumers parse the file
- Prefer local files over sockets for format.Parse
When it happens
Trigger: format.Parse peeks for footerPrefix, calls hr.ReadBytes('\n') for the closing line, and gets EOF or a read error — header truncated between the last stanza and the '---' MAC line.
Common situations: Files cut off mid-header by interrupted uploads/downloads; tiny garbage files; reading from a socket or pipe that closed early.
Related errors
- failed to read line: %w
- malformed body line %q: stanza ended without a short line no
- parsing age header:
- failed to parse header: %w
- failed to read header: %w
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/e1f0e016d87db91b.
Report an issue: GitHub.