{"record":{"id":"45d9a679e025f831","repo":"argoproj/argo-workflows","slug":"plugin-s-save-stream-failed-to-open-w","errorCode":null,"errorMessage":"plugin %s save stream failed to open: %w","messagePattern":"plugin (.+?) save stream failed to open: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"workflow/artifacts/plugin/plugin.go","lineNumber":251,"sourceCode":"// partially consumed and cannot be rewound.\nfunc (d *Driver) SaveStream(ctx context.Context, reader io.Reader, outputArtifact *wfv1.Artifact) error {\n\tsupported, err := d.supportsSaveStream(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !supported {\n\t\treturn d.saveStreamViaTempFile(ctx, reader, outputArtifact)\n\t}\n\n\t// Cancelled on every return path (including reader errors, which don't close the\n\t// stream themselves) so the server's blocking Recv() is released instead of\n\t// waiting on the caller's ctx, which may outlive this call.\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\n\tstream, err := d.client.SaveStream(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"plugin %s save stream failed to open: %w\", d.pluginName, err)\n\t}\n\n\t// Once the server aborts the stream, grpc-go makes Send return io.EOF instead\n\t// of the failure; the plugin's actual error is only retrievable via CloseAndRecv.\n\tsendFrame := func(req *artifact.SaveStreamArtifactRequest, action string) error {\n\t\tsendErr := stream.Send(req)\n\t\tif sendErr == nil {\n\t\t\treturn nil\n\t\t}\n\t\tif _, recvErr := stream.CloseAndRecv(); recvErr != nil {\n\t\t\treturn fmt.Errorf(\"plugin %s save stream failed %s: %w\", d.pluginName, action, recvErr)\n\t\t}\n\t\treturn fmt.Errorf(\"plugin %s save stream failed %s: %w\", d.pluginName, action, sendErr)\n\t}\n\n\tif sendErr := sendFrame(&artifact.SaveStreamArtifactRequest{OutputArtifact: convertToGRPC(outputArtifact)}, \"to send metadata\"); sendErr != nil {\n\t\treturn sendErr\n\t}","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/workflow/artifacts/plugin/plugin.go#L233-L269","documentation":"SaveStream could not even open the bidirectional SaveStream gRPC stream to the plugin. This happens before any data is sent, so it is a connection/RPC-establishment failure with the plugin service over its unix socket.","triggerScenarios":"Calling Driver.SaveStream when the plugin connection is broken (pod restarting, socket removed), the method is unimplemented despite the capability check passing (version skew), or the per-call context was already cancelled.","commonSituations":"Plugin restarted between the GetCapabilities check and SaveStream call, plugin build advertises streaming capability but lacks the RPC (custom build mismatch), controller-to-plugin socket deleted during plugin shutdown.","solutions":["Decode the wrapped gRPC status for the specific code (Unavailable = plugin down, Unimplemented = version mismatch)","Verify the plugin pod is running and the unix socket exists (ls -la on the socket path)","Confirm the deployed plugin build actually implements SaveStream and matches its advertised capabilities","Retry the artifact save once the plugin is healthy"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Verify the plugin socket exists and the plugin responds before SaveStream:\nif _, err := os.Stat(socketPath); err != nil {\n    return fmt.Errorf(\"plugin socket %s not present: %w\", socketPath, err)\n}","typeGuard":"func streamOpenFailed(err error) bool {\n    return strings.Contains(err.Error(), \"save stream failed to open\")\n}","tryCatchPattern":"err := driver.SaveStream(ctx, reader, artifact)\nif err != nil {\n    if streamOpenFailed(err) && grpcUnavailable(err) {\n        // plugin likely restarting; wait and retry with a FRESH reader\n        time.Sleep(backoff)\n        return reopenReaderAndSave()\n    }\n    return err\n}","preventionTips":["Check plugin pod health before long artifact-save phases","Align plugin builds so advertised capabilities match implemented RPCs","Avoid cancelling the caller context during save setup","Retry with a fresh reader — the original reader may have been partially consumed"],"tags":["grpc","plugin","streaming","artifacts"],"backgroundTag":"grpc-unavailable","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}