FiloSottile/age · error

failed to read line: %w

Error message

failed to read line: %w

What it means

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.

Source

Thrown at internal/format/format.go:192

	err error
}

func NewStanzaReader(r *bufio.Reader) *StanzaReader {
	return &StanzaReader{r: r}
}

func (r *StanzaReader) ReadStanza() (s *Stanza, err error) {
	// Read errors are unrecoverable.
	if r.err != nil {
		return nil, r.err
	}
	defer func() { r.err = err }()

	s = &Stanza{}

	line, err := r.r.ReadBytes('\n')
	if err != nil {
		return nil, fmt.Errorf("failed to read line: %w", err)
	}
	if !bytes.HasPrefix(line, stanzaPrefix) {
		return nil, fmt.Errorf("malformed stanza opening line: %q", line)
	}
	prefix, args := splitArgs(line)
	if prefix != string(stanzaPrefix) || len(args) < 1 {
		return nil, fmt.Errorf("malformed stanza: %q", line)
	}
	s.Type = args[0]
	s.Args = args[1:]

	for {
		line, err := r.r.ReadBytes('\n')
		if err != nil {
			return nil, fmt.Errorf("failed to read line: %w", err)
		}

		b, err := DecodeString(strings.TrimSuffix(string(line), "\n"))

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Check errors.Is(err, io.ErrUnexpectedEOF)/io.EOF: the input is truncated — obtain a complete file
  2. Verify the input is a real age v1 file and not corrupted or the wrong format
  3. Re-transfer/re-download the encrypted file and compare sizes/checksums

Example fix

// before
s, err := r.ReadStanza()
if err != nil { return err }
// after
s, err := r.ReadStanza()
if err != nil {
    if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) {
        return fmt.Errorf("truncated age input: %w", err)
    }
    return err
}
Defensive patterns

Strategy: try-catch

Try / catch

s, err := r.ReadStanza()
if err != nil {
    switch {
    case errors.Is(err, io.ErrUnexpectedEOF), errors.Is(err, io.EOF):
        return fmt.Errorf("truncated age input: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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