FiloSottile/age · error

malformed stanza opening line: %q

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.

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)

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.