{"record":{"id":"0783030b69946de3","repo":"FiloSottile/age","slug":"failed-to-read-line-w","errorCode":null,"errorMessage":"failed to read line: %w","messagePattern":"failed to read line: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/format/format.go","lineNumber":192,"sourceCode":"\terr error\n}\n\nfunc NewStanzaReader(r *bufio.Reader) *StanzaReader {\n\treturn &StanzaReader{r: r}\n}\n\nfunc (r *StanzaReader) ReadStanza() (s *Stanza, err error) {\n\t// Read errors are unrecoverable.\n\tif r.err != nil {\n\t\treturn nil, r.err\n\t}\n\tdefer func() { r.err = err }()\n\n\ts = &Stanza{}\n\n\tline, err := r.r.ReadBytes('\\n')\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read line: %w\", err)\n\t}\n\tif !bytes.HasPrefix(line, stanzaPrefix) {\n\t\treturn nil, fmt.Errorf(\"malformed stanza opening line: %q\", line)\n\t}\n\tprefix, args := splitArgs(line)\n\tif prefix != string(stanzaPrefix) || len(args) < 1 {\n\t\treturn nil, fmt.Errorf(\"malformed stanza: %q\", line)\n\t}\n\ts.Type = args[0]\n\ts.Args = args[1:]\n\n\tfor {\n\t\tline, err := r.r.ReadBytes('\\n')\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read line: %w\", err)\n\t\t}\n\n\t\tb, err := DecodeString(strings.TrimSuffix(string(line), \"\\n\"))","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/internal/format/format.go#L174-L210","documentation":"ReadStanza reads one line from the age stream with bufio ReadBytes('\\n'); any read failure — most often io.ErrUnexpectedEOF or io.EOF at a truncated stream — is wrapped as 'failed to read line'. The underlying cause is preserved via %w so errors.Is/As work.","triggerScenarios":"Parsing an age file/stream that ends mid-stanza, a closed/short pipe, or a network stream cut before the stanza header line arrives; also reading a non-age binary file whose read fails.","commonSituations":"Truncated downloads of .age files, wrong file passed to age decrypt, pipelines where the producer died early, testing with garbage input in TestParseErrorsDoNotIncludeLine-style tests.","solutions":["Check errors.Is(err, io.ErrUnexpectedEOF)/io.EOF: the input is truncated — obtain a complete file","Verify the input is a real age v1 file and not corrupted or the wrong format","Re-transfer/re-download the encrypted file and compare sizes/checksums"],"exampleFix":"// before\ns, err := r.ReadStanza()\nif err != nil { return err }\n// after\ns, err := r.ReadStanza()\nif err != nil {\n    if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) {\n        return fmt.Errorf(\"truncated age input: %w\", err)\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"s, err := r.ReadStanza()\nif err != nil {\n    switch {\n    case errors.Is(err, io.ErrUnexpectedEOF), errors.Is(err, io.EOF):\n        return fmt.Errorf(\"truncated age input: %w\", err)\n    }\n    return err\n}","preventionTips":["Verify file integrity (size/checksum) after downloads before decrypting","Use binary-safe transfers for .age files","Unwrap with errors.Is to distinguish truncation from format errors"],"tags":["age","io","truncation","parsing"],"backgroundTag":"unexpected-eof","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}