argoproj/argo-workflows · error
plugin %s save stream failed to read artifact content: %w
Error message
plugin %s save stream failed to read artifact content: %w
What it means
SaveStream failed while reading bytes from the caller-provided io.Reader (the artifact content source, e.g. a container file or another driver's stream). This is not a plugin or gRPC problem — the error after the prefix is the underlying reader's failure and the stream is abandoned without a final CloseAndRecv success path.
Source
Thrown at workflow/artifacts/plugin/plugin.go:288
buf := make([]byte, saveStreamChunkSize)
for {
n, readErr := reader.Read(buf)
if n > 0 {
// gRPC forbids mutating a message after Send (a lazy stats handler may
// read it later), so copy the chunk instead of aliasing buf, which the
// next Read overwrites.
chunk := make([]byte, n)
copy(chunk, buf[:n])
if sendErr := sendFrame(&artifact.SaveStreamArtifactRequest{Chunk: chunk}, "mid-transfer"); sendErr != nil {
return sendErr
}
}
if errors.Is(readErr, io.EOF) {
break
}
if readErr != nil {
return fmt.Errorf("plugin %s save stream failed to read artifact content: %w", d.pluginName, readErr)
}
}
resp, err := stream.CloseAndRecv()
if err != nil {
return fmt.Errorf("plugin %s save stream failed: %w", d.pluginName, err)
}
if !resp.Success {
return fmt.Errorf("plugin %s save stream failed: %s", d.pluginName, resp.Error)
}
return nil
}
// supportsSaveStream reports whether the plugin advertises streaming support.
// A plugin that predates GetCapabilities returns codes.Unimplemented, which maps to
// (false, nil) so SaveStream falls back to the buffered Save. Any other error is
// returned rather than silently downgrading to buffering a potentially large artifact
// to disk before the real failure would resurface via Save.View on GitHub (pinned to 35bff19146)
Solutions
- Look at the wrapped error after 'failed to read artifact content: ' for the real cause (file not found, closed pipe, I/O error)
- Verify the source file/path still exists and is readable on the executor at save time
- If saving from another stream, fix the upstream stream error first — this error is usually secondary
- Add retry logic around content generation if the source is transient (e.g. network-mounted storage)
Defensive patterns
Strategy: validation
Validate before calling
// Verify the source content is readable before streaming:
f, err := os.Open(sourcePath)
if err != nil {
return fmt.Errorf("artifact source unreadable before SaveStream: %w", err)
}
defer f.Close() Type guard
func isContentReadError(err error) bool {
return strings.Contains(err.Error(), "failed to read artifact content")
} Try / catch
if err := driver.SaveStream(ctx, reader, artifact); err != nil {
if isContentReadError(err) {
// local reader failed — no plugin involvement; fix or regenerate the source, then retry
return fmt.Errorf("local content source failed: %w", err)
}
return err
} Prevention
- Ensure artifact source files are not deleted before the save stage completes
- When saving from another stream (e.g. a plugin Load pipe), fix upstream errors first
- Use retryable/regeneratable sources for artifact content
- Check executor disk health if read errors recur
When it happens
Trigger: Calling Driver.SaveStream with a reader whose Read returns a non-EOF error mid-stream — e.g. the file being saved disappeared, a disk read error, or an upstream source stream (like an OpenStream pipe from error 500) failed.
Common situations: Saving an artifact from a container path deleted during execution, saving from a plugin Load pipe that itself errored (compounded stream errors), disk I/O errors on the executor, reader closed by another goroutine.
Related errors
- plugin %s stream write failed: %w
- plugin %s stream error: %s
- plugin %s save stream failed to open: %w
- plugin %s save stream failed %s: %w
- plugin %s save stream failed: %w
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/79eca308c5b75544.
Report an issue: GitHub.