FiloSottile/age · error

malformed stanza opening line

Error message

malformed stanza opening line: %q

What it means

A stanza must begin with the literal prefix '->'. ReadStanza checks bytes.HasPrefix after reading the line; if the line doesn't start with '->', the stream is not a stanza boundary and parsing stops with the offending line quoted (%q) in the error.

Solutions

  1. Confirm the input is an age v1 file produced by age encrypt
  2. Check the file wasn't edited, re-wrapped, or concatenated after encryption
  3. Look at the quoted line in the error to identify where format expectations diverge
Defensive patterns

Strategy: try-catch

Validate before calling

// quick pre-check that the input begins like an age file
head, _ := bufio.NewReader(f).Peek(2)
if string(head) == "" { return errors.New("empty input") }

Try / catch

s, err := r.ReadStanza()
if err != nil && strings.Contains(err.Error(), "malformed stanza opening line") {
    return fmt.Errorf("input is not an age v1 stream: %w", err)
}

Prevention

When it happens

Trigger: Parse/readStanza encountering a file where a stanza header is expected but the line starts with something else (e.g. '---' footer, 'wrong' MAC line, or random text); feeding a plaintext or foreign-format file to the age parser.

Common situations: Decrypting a file that isn't age-format, an encrypted file whose body was modified/reordered, concatenating age files, editors that mangled the header.

Understand the failure class

Related errors


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

Appendix: source

Thrown at internal/format/format.go:195

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"))
		if err != nil {
			if bytes.HasPrefix(line, footerPrefix) || bytes.HasPrefix(line, stanzaPrefix) {
				return nil, fmt.Errorf("malformed body line %q: stanza ended without a short line\nnote: this might be a file encrypted with an old beta version of age or rage; use age v1.0.0-beta6 or rage to decrypt it", line)

View on GitHub (pinned to b74dce4cdb)