{"record":{"id":"41f6e73f43dbeaf5","repo":"FiloSottile/age","slug":"failed-to-read-ok-stanza-v","errorCode":null,"errorMessage":"failed to read OK stanza: %v","messagePattern":"failed to read OK stanza: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/plugin.go","lineNumber":632,"sourceCode":"\nfunc (p *Plugin) recipientError(idx int, err error) int {\n\tif err := p.writeError([]string{\"recipient\", fmt.Sprint(idx)}, err); err != nil {\n\t\treturn p.fatalf(\"%v\", err)\n\t}\n\treturn 3\n}\n\nfunc (p *Plugin) identityError(idx int, err error) int {\n\tif err := p.writeError([]string{\"identity\", fmt.Sprint(idx)}, err); err != nil {\n\t\treturn p.fatalf(\"%v\", err)\n\t}\n\treturn 3\n}\n\nfunc expectOk(sr *format.StanzaReader) error {\n\tok, err := sr.ReadStanza()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read OK stanza: %v\", err)\n\t}\n\tif ok.Type != \"ok\" {\n\t\treturn fmt.Errorf(\"expected OK stanza, got %q\", ok.Type)\n\t}\n\treturn expectStanzaWithNoBody(ok, 0)\n}\n\nfunc readOkOrFail(sr *format.StanzaReader) (*format.Stanza, error) {\n\ts, err := sr.ReadStanza()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read response stanza: %v\", err)\n\t}\n\tswitch s.Type {\n\tcase \"fail\":\n\t\tif err := expectStanzaWithNoBody(s, 0); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%v\", err)\n\t\t}\n\t\treturn s, nil","sourceCodeStart":614,"sourceCodeEnd":650,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/plugin/plugin.go#L614-L650","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (plugin side) — missing stanza terminator\nfmt.Printf(\"-> ok\\n\")\n\n// after\nfmt.Printf(\"-> ok\\n\\n\")","handlingStrategy":"try-catch","validationCode":"cmd := exec.Command(pluginPath)\nif err := cmd.Start(); err != nil {\n    return fmt.Errorf(\"plugin not runnable: %w\", err)\n}","typeGuard":"func stanzaReadable(sr *format.StanzaReader) bool {\n    _, err := sr.ReadStanza()\n    return err == nil\n}","tryCatchPattern":"if err := expectOk(sr); err != nil {\n    if strings.Contains(err.Error(), \"failed to read OK stanza\") {\n        // plugin died or malformed framing; capture stderr and fail with context\n        return fmt.Errorf(\"plugin %s did not acknowledge: %w (stderr: %s)\", pluginName, err, stderrBuf.String())\n    }\n    return err\n}","preventionTips":["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"],"tags":["age","plugin-protocol","io","broken-plugin"],"backgroundTag":"plugin-protocol-io-failure","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}