{"record":{"id":"e010bbca0167bd16","repo":"argoproj/argo-workflows","slug":"plugin-s-save-failed-s","errorCode":null,"errorMessage":"plugin %s save failed: %s","messagePattern":"plugin (.+?) save failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"workflow/artifacts/plugin/plugin.go","lineNumber":215,"sourceCode":"\t\t\t}\n\t\t}\n\t}()\n\n\treturn reader, nil\n}\n\n// Save implements ArtifactDriver.Save by calling the plugin service\nfunc (d *Driver) Save(ctx context.Context, path string, outputArtifact *wfv1.Artifact) error {\n\tgrpcArtifact := convertToGRPC(outputArtifact)\n\tresp, err := d.client.Save(ctx, &artifact.SaveArtifactRequest{\n\t\tPath:           path,\n\t\tOutputArtifact: grpcArtifact,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"plugin %s save failed: %w\", d.pluginName, err)\n\t}\n\tif !resp.Success {\n\t\treturn fmt.Errorf(\"plugin %s save failed: %s\", d.pluginName, resp.Error)\n\t}\n\treturn nil\n}\n\n// saveStreamChunkSize is the size of each chunk sent over the streaming SaveStream RPC.\n// 2MiB stays well under gRPC's default 4MiB max message size while keeping the\n// per-chunk marshal/syscall overhead low for multi-GB artifacts.\nconst saveStreamChunkSize = 2 * 1024 * 1024\n\n// SaveStream implements ArtifactDriver.SaveStream. If the plugin advertises\n// streaming support (per GetCapabilities), the reader is streamed directly with no\n// local buffering. Otherwise it falls back to buffering to a temp file and calling\n// the existing unary Save, so a plugin that predates streaming keeps working.\n//\n// Capability is checked before reader is touched: once GetCapabilities confirms\n// streaming support and chunks start being sent, a mid-stream failure is returned\n// as an error rather than retried via the fallback, since the reader may already be\n// partially consumed and cannot be rewound.","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/workflow/artifacts/plugin/plugin.go#L197-L233","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the message after 'plugin <name> save failed: ' — it is the plugin's own error string explaining the failure","Check the plugin's logs for the full stack trace behind that error","Verify destination storage credentials, bucket existence, and write permissions from the plugin pod","Fix the artifact spec (path, key, flags) if the plugin rejected it as invalid"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Validate the artifact spec and destination before calling Save:\nif artifact.ArtifactLocation == nil || artifact.S3 == nil && artifact.GCS == nil {\n    return fmt.Errorf(\"artifact has no plugin-supported location configured\")\n}\nif path == \"\" {\n    return fmt.Errorf(\"artifact save path is empty\")\n}","typeGuard":"func isPluginAppError(err error) (string, bool) {\n    s := err.Error()\n    if strings.Contains(s, \"save failed: \") && !errors.Is(err, nil) {\n        idx := strings.LastIndex(s, \"save failed: \")\n        return s[idx+len(\"save failed: \"):], true\n    }\n    return \"\", false\n}","tryCatchPattern":"if err := driver.Save(ctx, path, artifact); err != nil {\n    if msg, ok := isPluginAppError(err); ok {\n        logger.Error(\"plugin rejected save\", \"reason\", msg)\n        // fix artifact config/credentials; do NOT blind-retry\n    }\n    return err\n}","preventionTips":["Read the plugin's error string — it names the exact rejection cause","Validate destination bucket/prefix existence and plugin credentials ahead of runs","Keep plugin storage config in a checked-in, reviewed configmap","Dry-run the plugin's save with a test artifact after config changes"],"tags":["plugin","artifacts","save","application-error"],"backgroundTag":"plugin-save-failed","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"}