{"record":{"id":"3a53e0794ad79ce9","repo":"FiloSottile/age","slug":"failed-to-read-header-w-3a53e0","errorCode":null,"errorMessage":"failed to read header: %w","messagePattern":"failed to read header: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/inspect/inspect.go","lineNumber":52,"sourceCode":"\tdata := &Metadata{\n\t\tVersion:     \"age-encryption.org/v1\",\n\t\tPostquantum: \"yes\",\n\t}\n\n\ttr := &trackReader{r: r}\n\tbr := bufio.NewReader(tr)\n\tconst maxWhitespace = 1024\n\tstart, _ := br.Peek(maxWhitespace + len(armor.Header))\n\tif strings.HasPrefix(string(bytes.TrimSpace(start)), armor.Header) {\n\t\tr = armor.NewReader(br)\n\t\tdata.Armor = true\n\t} else {\n\t\tr = br\n\t}\n\n\thdr, rest, err := format.Parse(r)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read header: %w\", err)\n\t}\n\n\tbuf := &bytes.Buffer{}\n\tif err := hdr.Marshal(buf); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to re-serialize header: %w\", err)\n\t}\n\tdata.Sizes.Header = int64(buf.Len())\n\n\tfor _, s := range hdr.Recipients {\n\t\tdata.StanzaTypes = append(data.StanzaTypes, s.Type)\n\t\tswitch s.Type {\n\t\tcase \"X25519\", \"ssh-rsa\", \"ssh-ed25519\", \"p256tag\", \"piv-p256\":\n\t\t\tdata.Postquantum = \"no\"\n\t\tcase \"mlkem768x25519\", \"scrypt\", \"mlkem768p256tag\":\n\t\t\t// Keep \"yes\".\n\t\tdefault:\n\t\t\tif data.Postquantum != \"no\" {\n\t\t\t\tdata.Postquantum = \"unknown\"","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/internal/inspect/inspect.go#L34-L70","documentation":"internal/inspect.Inspect parses the age header via format.Parse and wraps any parse failure as 'failed to read header'. Since Inspect reports metadata about a file, this means the input is not a well-formed age file or is truncated in the header.","triggerScenarios":"inspect.Inspect (called by the age-inspect command's main, or tests) calls format.Parse(r) and err != nil — non-age input, corrupted or truncated header.","commonSituations":"Running age-inspect on plaintext files, wrong extensions, partially uploaded files, or armored files passed without armor handling.","solutions":["Confirm the target file is age-encrypted and complete before inspecting","If PEM-armored, inspect the decoded stream or ensure armor handling is used","Print the wrapped cause (errors.As *age.ParseError) to identify the offending line","Re-download the file if the size looks short"],"exampleFix":"// before\ndata, err := inspect.Inspect(f, size) // failed to read header\n// after\nhead, _ := bufio.NewReader(f).Peek(len(\"age-encryption.org\"))\nif !bytes.HasPrefix(head, []byte(\"age-encryption\")) { return fmt.Errorf(\"not an age file\") }\nf.Seek(0, 0)\ndata, err := inspect.Inspect(f, size)","handlingStrategy":"try-catch","validationCode":"head, _ := bufio.NewReader(f).Peek(len(\"age-encryption.org/v1\"))\nif !bytes.HasPrefix(head, []byte(\"age-encryption.org/v1\")) { return errors.New(\"age-inspect: not an age file\") }","typeGuard":"var pe *age.ParseError\nif errors.As(err, &pe) { /* header-level failure from format.Parse */ }","tryCatchPattern":"data, err := inspect.Inspect(f, size)\nif err != nil {\n    var pe *age.ParseError\n    if errors.As(err, &pe) { return fmt.Errorf(\"cannot inspect: %w\", pe.Unwrap()) }\n    return err\n}","preventionTips":["Check that the input starts with the age intro line before inspecting","Handle armored input separately (armor.NewReader)","Validate file completeness before metadata analysis"],"tags":["inspect","header-parsing","cli"],"backgroundTag":"malformed-header","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}