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
- Upgrade or replace the plugin binary so control stanzas carry no body
- Log the stanza body bytes at failure to identify what the plugin appended
- If you authored the plugin, remove any writes between the stanza line and the blank terminator line
- 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
- Terminate every control stanza with a blank line in plugin code
- Never interleave human-readable output with stanzas on stdout
- Test the plugin against the reference client's strict parser
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
- %s stanza has %d arguments, want %d
- %s stanza has 0 bytes of body, want >0
- expected OK stanza, got %q
- expected ok or fail stanza, got %q
- invalid ssh-rsa recipient block
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/cf6c38cba8de6a76.
Report an issue: GitHub.