argoproj/argo-workflows · warning
plugin %s stream write failed: %w
Error message
plugin %s stream write failed: %w
What it means
This error occurs in the OpenStream pump goroutine when writing a received plugin chunk into the io.Pipe fails via writer.Write. Since the pipe's read side is handed to the caller, a write error almost always means the consumer closed or stopped reading the pipe (or the pipe was already closed with an error). The driver wraps the write error with the plugin name and tears down the stream.
Source
Thrown at workflow/artifacts/plugin/plugin.go:194
for {
resp, err := stream.Recv()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
writer.CloseWithError(fmt.Errorf("plugin %s stream receive failed: %w", d.pluginName, err))
return
}
if resp.Error != "" {
writer.CloseWithError(fmt.Errorf("plugin %s stream error: %s", d.pluginName, resp.Error))
return
}
if resp.IsEnd {
break
}
if len(resp.Data) > 0 {
if _, writeErr := writer.Write(resp.Data); writeErr != nil {
writer.CloseWithError(fmt.Errorf("plugin %s stream write failed: %w", d.pluginName, writeErr))
return
}
}
}
}()
return reader, nil
}
// Save implements ArtifactDriver.Save by calling the plugin service
func (d *Driver) Save(ctx context.Context, path string, outputArtifact *wfv1.Artifact) error {
grpcArtifact := convertToGRPC(outputArtifact)
resp, err := d.client.Save(ctx, &artifact.SaveArtifactRequest{
Path: path,
OutputArtifact: grpcArtifact,
})
if err != nil {
return fmt.Errorf("plugin %s save failed: %w", d.pluginName, err)View on GitHub (pinned to 35bff19146)
Solutions
- Check whether your code closes the returned ReadCloser before fully reading it — early Close triggers this
- Ensure you do not close the reader while the plugin stream is still being consumed
- If abandoning a stream, cancel the context passed to OpenStream so the goroutine exits cleanly
- Inspect the wrapped writeErr after 'stream write failed: ' for the actual pipe error (usually io.ErrClosedPipe)
Defensive patterns
Strategy: try-catch
Type guard
func isClosedPipe(err error) bool {
return errors.Is(err, io.ErrClosedPipe) || errors.Is(err, os.ErrClosed)
} Try / catch
reader, err := driver.OpenStream(ctx, artifact)
if err != nil { return err }
_, copyErr := io.Copy(dst, reader)
reader.Close()
if copyErr != nil {
if isClosedPipe(copyErr) {
// consumer closed early; ensure ctx cancel to stop the driver goroutine
cancel()
return nil // or handle as intentional early termination
}
return copyErr
} Prevention
- Never close the OpenStream ReadCloser before reading to completion unless intentionally aborting
- Cancel the context when abandoning a stream so the pump goroutine exits cleanly
- Avoid double-closing the returned reader
- Check for early returns in your copy loop (deadlines, size limits) that close the pipe mid-stream
When it happens
Trigger: Calling Driver.OpenStream and then closing or abandoning the returned io.ReadCloser before the plugin finishes streaming, so the driver goroutine's writer.Write fails with io.ErrClosedPipe.
Common situations: Caller uses io.Copy with a context timeout and gives up early; artifact loading code errors elsewhere and closes the reader; double-close of the ReadCloser; process shutting down mid-stream.
Related errors
- plugin %s save stream failed to read artifact content: %w
- failed to buffer stream to temp file: %w
- plugin %s stream error: %s
- plugin %s save stream failed to open: %w
- plugin %s save stream failed %s: %w
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/43a6d516a75bdb3a.
Report an issue: GitHub.