FiloSottile/age · error
expected OK stanza, got %q
Error message
expected OK stanza, got %q
What it means
The client successfully read a stanza but its Type was not 'ok'. expectOk is used after commands that must be acknowledged with a bare 'ok' stanza (recipient/identity wrap, error reporting). Receiving any other stanza type means the plugin responded out of protocol order or with an unexpected reply.
Source
Thrown at plugin/plugin.go:635
return p.fatalf("%v", err)
}
return 3
}
func (p *Plugin) identityError(idx int, err error) int {
if err := p.writeError([]string{"identity", fmt.Sprint(idx)}, err); err != nil {
return p.fatalf("%v", err)
}
return 3
}
func expectOk(sr *format.StanzaReader) error {
ok, err := sr.ReadStanza()
if err != nil {
return fmt.Errorf("failed to read OK stanza: %v", err)
}
if ok.Type != "ok" {
return fmt.Errorf("expected OK stanza, got %q", ok.Type)
}
return expectStanzaWithNoBody(ok, 0)
}
func readOkOrFail(sr *format.StanzaReader) (*format.Stanza, error) {
s, err := sr.ReadStanza()
if err != nil {
return nil, fmt.Errorf("failed to read response stanza: %v", err)
}
switch s.Type {
case "fail":
if err := expectStanzaWithNoBody(s, 0); err != nil {
return nil, fmt.Errorf("%v", err)
}
return s, nil
case "ok":
return s, nil
default:View on GitHub (pinned to b74dce4cdb)
Solutions
- Log the received stanza type to see what the plugin actually replied
- Confirm the plugin supports the command that was just sent (wrap/unwrap recipient-v1/identity-v1)
- Upgrade the plugin to a protocol-conformant version
- Ensure the plugin never writes human-readable text to stdout (stdout is protocol-only; use stderr)
Example fix
// before (plugin side)
fmt.Printf("cannot handle this recipient\n\n") // parsed as a stanza
// after
fmt.Fprintf(os.Stderr, "cannot handle this recipient\n")
fmt.Printf("-> fail\n\n") Defensive patterns
Strategy: type-guard
Type guard
func isOkStanza(s *format.Stanza) bool {
return s != nil && s.Type == "ok"
} Try / catch
if err := expectOk(sr); err != nil {
var unexpected *unexpectedStanzaError
if errors.As(err, &unexpected) {
log.Printf("plugin replied %q; check plugin protocol support", unexpected.Type)
}
return err
} Prevention
- Confirm plugin supports each command before sending it
- Route all plugin diagnostics to stderr, never stdout
- Keep plugin and client protocol revisions in sync
When it happens
Trigger: After writeError sends an 'error' stanza, the plugin replies with something other than 'ok'; a plugin replies 'fail' or 'unsupported' where 'ok' is mandated; the client and plugin disagree on which command is in flight, so the reply is the answer to a previous command.
Common situations: Plugin does not implement the full plugin protocol (e.g. no support for the unsupported/error handshake); version mismatch causing off-by-one command sequencing; plugin echoing diagnostics onto stdout that get parsed as a stanza line.
Related errors
- expected ok or fail stanza, got %q
- %s stanza has %d arguments, want %d
- %s stanza has %d bytes of body, want 0
- %s stanza has 0 bytes of body, want >0
- invalid ssh-rsa recipient block
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/324c6bb4b1e7da82.
Report an issue: GitHub.