argoproj/argo-workflows · error

plugin %s save failed: %s

Error message

plugin %s save failed: %s

What it means

This variant fires when the plugin's Save RPC completed successfully at the gRPC level but the response reports Success=false with an application-level Error string. The error message contains the plugin's own error text after 'save failed: ', so it is plugin-side business logic failure (e.g. it could not write to the destination), not a transport problem.

Source

Thrown at workflow/artifacts/plugin/plugin.go:215

			}
		}
	}()

	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)
	}
	if !resp.Success {
		return fmt.Errorf("plugin %s save failed: %s", d.pluginName, resp.Error)
	}
	return nil
}

// saveStreamChunkSize is the size of each chunk sent over the streaming SaveStream RPC.
// 2MiB stays well under gRPC's default 4MiB max message size while keeping the
// per-chunk marshal/syscall overhead low for multi-GB artifacts.
const saveStreamChunkSize = 2 * 1024 * 1024

// SaveStream implements ArtifactDriver.SaveStream. If the plugin advertises
// streaming support (per GetCapabilities), the reader is streamed directly with no
// local buffering. Otherwise it falls back to buffering to a temp file and calling
// the existing unary Save, so a plugin that predates streaming keeps working.
//
// Capability is checked before reader is touched: once GetCapabilities confirms
// streaming support and chunks start being sent, a mid-stream failure is returned
// as an error rather than retried via the fallback, since the reader may already be
// partially consumed and cannot be rewound.

View on GitHub (pinned to 35bff19146)

Solutions

  1. Read the message after 'plugin <name> save failed: ' — it is the plugin's own error string explaining the failure
  2. Check the plugin's logs for the full stack trace behind that error
  3. Verify destination storage credentials, bucket existence, and write permissions from the plugin pod
  4. Fix the artifact spec (path, key, flags) if the plugin rejected it as invalid
Defensive patterns

Strategy: validation

Validate before calling

// Validate the artifact spec and destination before calling Save:
if artifact.ArtifactLocation == nil || artifact.S3 == nil && artifact.GCS == nil {
    return fmt.Errorf("artifact has no plugin-supported location configured")
}
if path == "" {
    return fmt.Errorf("artifact save path is empty")
}

Type guard

func isPluginAppError(err error) (string, bool) {
    s := err.Error()
    if strings.Contains(s, "save failed: ") && !errors.Is(err, nil) {
        idx := strings.LastIndex(s, "save failed: ")
        return s[idx+len("save failed: "):], true
    }
    return "", false
}

Try / catch

if err := driver.Save(ctx, path, artifact); err != nil {
    if msg, ok := isPluginAppError(err); ok {
        logger.Error("plugin rejected save", "reason", msg)
        // fix artifact config/credentials; do NOT blind-retry
    }
    return err
}

Prevention

When it happens

Trigger: Calling Driver.Save and receiving resp.Success == false — the plugin processed the request but failed internally and returned its error in-band in resp.Error.

Common situations: Plugin cannot authenticate to object storage, destination bucket/prefix does not exist or is not writable, size/quota limits hit on the backend, plugin rejected the artifact path or metadata.

Related errors


AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03). Data as JSON: /api/errors/e010bbca0167bd16. Report an issue: GitHub.