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, nil

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Run the plugin binary manually with the same stdin to see if it crashes or exits early
  2. Check the plugin's stderr for a panic or error message preceding the stream failure
  3. Verify the plugin terminates every stanza with a blank line
  4. 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

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


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