FiloSottile/age · error

malformed body line %q: stanza ended without a short line no

Error message

malformed body line %q: stanza ended without a short line
note: 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

What it means

A stanza body line failed base64 decoding, and the line itself started with a footer or stanza prefix — meaning a new stanza/footer began without the mandatory short (empty-ish) line that terminates the previous stanza. The message notes files produced by age v1.0.0-beta1–5, which lacked that short line.

Source

Thrown at internal/format/format.go:213

		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)
			}
			return nil, errorf("malformed body line %q: %v", line, err)
		}
		if len(b) > BytesPerLine {
			return nil, errorf("malformed body line %q: too long", line)
		}
		s.Body = append(s.Body, b...)
		if len(b) < BytesPerLine {
			// A stanza body always ends with a short line.
			return s, nil
		}
	}
}

type ParseError struct {
	err error
}

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Decrypt the file with age v1.0.0-beta6 or rage (as the note suggests), re-encrypt with a current version
  2. Upgrade the encrypting tool so all new files use the stable v1 format
  3. If the file is current-format, treat this as corruption: re-transfer the file
  4. Do not hand-edit age headers; regenerate instead

Example fix

// before
$ age -d -i key.txt file.age   # fails: malformed body line "---": stanza ended without a short line
// after
$ rage file.age > out.txt      # or age v1.0.0-beta6
$ age -e -r <new-pubkey> out.txt > file-v1.age
Defensive patterns

Strategy: validation

Validate before calling

head, _ := bufio.NewReader(f).Peek(64)
// beta age files look similar but stanzas lack the short terminator line
if !bytes.HasPrefix(head, []byte("age-encryption.org/v1")) { return errors.New("not a v1 age file") }

Type guard

var pe *age.ParseError
if errors.As(err, &pe) && strings.Contains(pe.Error(), "old beta version") { /* legacy beta format */ }

Prevention

When it happens

Trigger: format.ReadStanza decodes a body line, DecodeString fails, and bytes.HasPrefix(line, footerPrefix) || bytes.HasPrefix(line, stanzaPrefix) is true — a stanza ended without a short line.

Common situations: Decrypting files generated by old beta versions (age < v1.0.0-beta6 or matching rage betas); hand-edited header files; corruption that made a short line disappear.

Understand the failure class

Related errors


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