FiloSottile/age · error

%s stanza has %d bytes of body, want 0

Error message

%s stanza has %d bytes of body, want 0

What it means

expectStanzaWithNoBody rejects stanzas that were required to be body-less but arrived with payload bytes after the argument line. In the age plugin protocol, control stanzas like 'ok' and 'fail' must have an empty body; extra bytes indicate a malformed reply. This protects the client from silently ignoring unexpected data.

Source

Thrown at plugin/plugin.go:593

// fatalInteractf prints the error to stderr and sets the broken flag, so the
// Wrap/Unwrap caller can exit with an error.
func (p *Plugin) fatalInteractf(format string, args ...any) error {
	p.broken = true
	fmt.Fprintf(p.stderr, format, args...)
	return fmt.Errorf(format, args...)
}

func (p *Plugin) fatalf(format string, args ...any) int {
	fmt.Fprintf(p.stderr, format, args...)
	return 1
}

func expectStanzaWithNoBody(s *format.Stanza, wantArgs int) error {
	if len(s.Args) != wantArgs {
		return fmt.Errorf("%s stanza has %d arguments, want %d", s.Type, len(s.Args), wantArgs)
	}
	if len(s.Body) != 0 {
		return fmt.Errorf("%s stanza has %d bytes of body, want 0", s.Type, len(s.Body))
	}
	return nil
}

func expectStanzaWithBody(s *format.Stanza, wantArgs int) error {
	if err := expectStanzaWithAnyBody(s, wantArgs); err != nil {
		return err
	}
	if len(s.Body) == 0 {
		return fmt.Errorf("%s stanza has 0 bytes of body, want >0", s.Type)
	}
	return nil
}

func expectStanzaWithAnyBody(s *format.Stanza, wantArgs int) error {
	if len(s.Args) != wantArgs {
		return fmt.Errorf("%s stanza has %d arguments, want %d", s.Type, len(s.Args), wantArgs)
	}

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Upgrade or replace the plugin binary so control stanzas carry no body
  2. Log the stanza body bytes at failure to identify what the plugin appended
  3. If you authored the plugin, remove any writes between the stanza line and the blank terminator line
  4. Verify no middle layer (pipe wrapper, logging filter) is injecting bytes into the plugin's stdout

Example fix

// before (plugin side)
fmt.Printf("-> ok\nextra explanation text\n\n")

// after
fmt.Printf("-> ok\n\n")
Defensive patterns

Strategy: validation

Validate before calling

if len(stanza.Body) != 0 {
    return fmt.Errorf("%s must have empty body, got %d bytes", stanza.Type, len(stanza.Body))
}

Type guard

func isBodyless(s *format.Stanza) bool {
    return s != nil && len(s.Body) == 0
}

Prevention

When it happens

Trigger: A reply stanza read by RecipientV1, IdentityV1, DisplayMessage, Confirm, expectOk, or readOkOrFail passed the argument-count check but s.Body had length > 0; e.g. an 'ok' stanza followed by data lines before the terminating empty line.

Common situations: A plugin that writes a trailing newline/text after an 'ok'; a plugin flushing leftover stdout buffer into a control stanza; protocol-version drift where a newer plugin attaches a reason body to 'fail' or 'ok'.

Related errors


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