FiloSottile/age · error
failed to read OK stanza: %v
Error message
failed to read OK stanza: %v
What it means
expectOk reads the next stanza from the plugin and wraps any reader failure (including EOF or malformed stanza framing) with this message. The client expected an 'ok' reply to a command it sent, but the stanza stream ended or could not be parsed before one arrived. This usually means the plugin crashed, exited early, or emitted invalid stanza framing.
Source
Thrown at plugin/plugin.go:632
func (p *Plugin) recipientError(idx int, err error) int {
if err := p.writeError([]string{"recipient", fmt.Sprint(idx)}, err); err != nil {
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, nilView on GitHub (pinned to b74dce4cdb)
Solutions
- Run the plugin binary manually with the same stdin to see if it crashes or exits early
- Check the plugin's stderr for a panic or error message preceding the stream failure
- Verify the plugin terminates every stanza with a blank line
- Upgrade/reinstall the plugin; test with a known-good plugin to isolate the cause
Example fix
// before (plugin side) — missing stanza terminator
fmt.Printf("-> ok\n")
// after
fmt.Printf("-> ok\n\n") Defensive patterns
Strategy: try-catch
Validate before calling
cmd := exec.Command(pluginPath)
if err := cmd.Start(); err != nil {
return fmt.Errorf("plugin not runnable: %w", err)
} Type guard
func stanzaReadable(sr *format.StanzaReader) bool {
_, err := sr.ReadStanza()
return err == nil
} Try / catch
if err := expectOk(sr); err != nil {
if strings.Contains(err.Error(), "failed to read OK stanza") {
// plugin died or malformed framing; capture stderr and fail with context
return fmt.Errorf("plugin %s did not acknowledge: %w (stderr: %s)", pluginName, err, stderrBuf.String())
}
return err
} Prevention
- Capture plugin stderr alongside stdout for diagnostics
- Test the plugin binary directly with crafted stdin before integration
- Set a timeout so a hung plugin surfaces as a timeout, not an EOF
When it happens
Trigger: After RecipientV1/IdentityV1 sends a recipient/identity stanza, or after writeError reports an error, the client reads the reply; ReadStanza returns an error (unexpected EOF, missing terminating blank line, invalid stanza line) instead of an 'ok' stanza.
Common situations: Plugin binary panics/exits before writing its reply; plugin writes a stanza without the terminating empty line; plugin stdout closed early due to a broken pipe; shell wrapper mangling output.
Related errors
- failed to read response stanza: %v
- failed to read unsupported stanza: %v
- failed to read line: %w
- malformed file-key stanza: unexpected arguments count
- malformed file-key stanza: invalid index
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/41f6e73f43dbeaf5.
Report an issue: GitHub.