{"record":{"id":"e482409a8b90c050","repo":"argoproj/argo-workflows","slug":"plugin-s-delete-failed-w","errorCode":null,"errorMessage":"plugin %s delete failed: %w","messagePattern":"plugin (.+?) delete failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"workflow/artifacts/plugin/plugin.go","lineNumber":333,"sourceCode":"\treturn resp.GetSupportsSaveStream(), nil\n}\n\n// saveStreamViaTempFile is the fallback used when the plugin doesn't implement\n// streaming SaveStream: buffer to a temp file and call the existing unary Save.\nfunc (d *Driver) saveStreamViaTempFile(ctx context.Context, reader io.Reader, outputArtifact *wfv1.Artifact) error {\n\treturn common.SaveStreamViaTempFile(reader, \"plugin-upload-*\", func(path string) error {\n\t\treturn d.Save(ctx, path, outputArtifact)\n\t})\n}\n\n// Delete implements ArtifactDriver.Delete by calling the plugin service\nfunc (d *Driver) Delete(ctx context.Context, artifactRef *wfv1.Artifact) error {\n\tgrpcArtifact := convertToGRPC(artifactRef)\n\tresp, err := d.client.Delete(ctx, &artifact.DeleteArtifactRequest{\n\t\tArtifact: grpcArtifact,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"plugin %s delete failed: %w\", d.pluginName, err)\n\t}\n\tif !resp.Success {\n\t\treturn fmt.Errorf(\"plugin %s delete failed: %s\", d.pluginName, resp.Error)\n\t}\n\treturn nil\n}\n\n// ListObjects implements ArtifactDriver.ListObjects by calling the plugin service\nfunc (d *Driver) ListObjects(ctx context.Context, artifactRef *wfv1.Artifact) ([]string, error) {\n\tgrpcArtifact := convertToGRPC(artifactRef)\n\tresp, err := d.client.ListObjects(ctx, &artifact.ListObjectsRequest{\n\t\tArtifact: grpcArtifact,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"plugin %s list objects failed: %w\", d.pluginName, err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"plugin %s list objects failed: %s\", d.pluginName, resp.Error)","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/workflow/artifacts/plugin/plugin.go#L315-L351","documentation":"Driver.Delete forwards an artifact deletion to the artifact plugin over gRPC (client.Delete). When the RPC itself fails — transport error, context deadline, Unimplemented, etc. — the error is wrapped as \"plugin %s delete failed: %w\". This is distinct from error 512, which fires when the RPC succeeds but the plugin reports a logical failure via resp.Success=false.","triggerScenarios":"Calling Driver.Delete(ctx, artifactRef) when the plugin's Delete RPC returns a gRPC error: plugin unreachable (Unavailable), method not implemented (Unimplemented, old plugin binary), ctx deadline exceeded, or the artifact ref failed conversion/serialization causing an InvalidArgument.","commonSituations":"Garbage-collecting artifacts after workflow deletion with the plugin offline; plugin upgraded to a version without Delete (Unimplemented); artifact reference pointing at storage the plugin can't reach, surfaced as a deadline or internal error; network partition between controller and plugin service.","solutions":["Check the wrapped (%w) gRPC status: Unavailable means fix plugin connectivity/restart the plugin; Unimplemented means upgrade the plugin to a build that implements Delete.","Verify the artifact reference is complete and valid (key/bucket fields) so the plugin receives a well-formed request.","Check plugin logs for the server-side cause (storage credentials revoked, object already gone).","If deletion is best-effort garbage collection, tolerate the error and retry later; Delete is idempotent for most storage backends."],"exampleFix":"// caller tolerating best-effort cleanup\n// before:\nif err := driver.Delete(ctx, art); err != nil { return err }\n// after:\nif err := driver.Delete(ctx, art); err != nil {\n\tif status.Code(err) == codes.Unimplemented || status.Code(err) == codes.Unavailable {\n\t\tlog.Warn(ctx, \"defer artifact delete\", \"err\", err) // retry via GC later\n\t\treturn nil\n\t}\n\treturn err\n}","handlingStrategy":"retry","validationCode":"// validate artifact ref before delete\nfunc validForDelete(a *wfv1.Artifact) error {\n\tif a == nil || a.GetName() == \"\" {\n\t\treturn errors.New(\"artifact ref incomplete: name required\")\n\t}\n\treturn nil\n}","typeGuard":"func isRetryableDeleteErr(err error) bool {\n\tswitch status.Code(errors.Unwrap(err)) {\n\tcase codes.Unavailable, codes.DeadlineExceeded, codes.ResourceExhausted:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}","tryCatchPattern":"err := driver.Delete(ctx, art)\nif err != nil {\n\tif isRetryableDeleteErr(err) {\n\t\treturn retry.WithExponentialBackoff(ctx, func() error { return driver.Delete(ctx, art) }, 3)\n\t}\n\treturn fmt.Errorf(\"artifact delete not retryable: %w\", err)\n}","preventionTips":["Treat artifact deletion as idempotent best-effort GC; queue failures for retry rather than failing workflows.","Keep plugin versions in lockstep with the controller so Delete is always implemented.","Alert on sustained Unavailable errors from the plugin service.","Log the artifact key with the error to make cleanup retries traceable."],"tags":["grpc","plugin","artifacts","delete"],"backgroundTag":"grpc-rpc-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"}