{"record":{"id":"e1f0e016d87db91b","repo":"FiloSottile/age","slug":"failed-to-read-header-w","errorCode":null,"errorMessage":"failed to read header: %w","messagePattern":"failed to read header: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/format/format.go","lineNumber":339,"sourceCode":"\t\t}\n\t\treturn nil, nil, errorf(\"unexpected intro, expected %q\", intro)\n\t}\n\n\tsr := &StanzaReader{r: hr}\n\tfor {\n\t\tpeek, err := hr.Peek(len(footerPrefix))\n\t\tif err != nil {\n\t\t\t// headerReader errors are already ParseErrors; don't nest the prefix.\n\t\t\tif _, ok := err.(*ParseError); ok {\n\t\t\t\treturn nil, nil, err\n\t\t\t}\n\t\t\treturn nil, nil, errorf(\"failed to read header: %w\", err)\n\t\t}\n\n\t\tif bytes.Equal(peek, footerPrefix) {\n\t\t\tline, err := hr.ReadBytes('\\n')\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"failed to read header: %w\", err)\n\t\t\t}\n\n\t\t\tprefix, args := splitArgs(line)\n\t\t\tif prefix != string(footerPrefix) || len(args) != 1 {\n\t\t\t\treturn nil, nil, errorf(\"malformed closing line: %q\", line)\n\t\t\t}\n\t\t\th.MAC, err = DecodeString(args[0])\n\t\t\tif err != nil || len(h.MAC) != 32 {\n\t\t\t\treturn nil, nil, errorf(\"malformed closing line %q: %v\", line, err)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tif len(h.Recipients) == maxRecipientStanzas {\n\t\t\treturn nil, nil, errorf(\"header contains more than %d recipient stanzas\", maxRecipientStanzas)\n\t\t}\n\n\t\ts, err := sr.ReadStanza()\n\t\tif err != nil {","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/internal/format/format.go#L321-L357","documentation":"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.","triggerScenarios":"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.","commonSituations":"Files cut off mid-header by interrupted uploads/downloads; tiny garbage files; reading from a socket or pipe that closed early.","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"],"exampleFix":"// before\nhdr, _, err := format.Parse(f) // failed to read header: unexpected EOF\n// after\nst, _ := f.Stat()\nif st.Size() < 100 { return fmt.Errorf(\"refusing parse: file is %d bytes\", st.Size()) }\nhdr, _, err := format.Parse(f)","handlingStrategy":"try-catch","validationCode":"st, err := os.Stat(path)\nif err != nil { return err }\nif st.Size() < 100 { return fmt.Errorf(\"file too small to contain a full age header\") }","typeGuard":"var pe *age.ParseError\nif errors.As(err, &pe) && errors.Is(pe.Unwrap(), io.ErrUnexpectedEOF) { /* header cut before closing line */ }","tryCatchPattern":"hdr, body, err := format.Parse(r)\nif err != nil {\n    if errors.Is(err, io.ErrUnexpectedEOF) { return fmt.Errorf(\"file truncated in header: %w\", err) }\n    return err\n}","preventionTips":["Confirm complete transfer (size/hash) before parsing","Flush and close writers before consumers parse the file","Prefer local files over sockets for format.Parse"],"tags":["io","eof","header-parsing","truncated-file"],"backgroundTag":"unexpected-eof","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}