{"record":{"id":"8c736266582129e0","repo":"argoproj/argo-workflows","slug":"plugin-s-get-capabilities-failed-w","errorCode":null,"errorMessage":"plugin %s get capabilities failed: %w","messagePattern":"plugin (.+?) get capabilities failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"workflow/artifacts/plugin/plugin.go","lineNumber":313,"sourceCode":"\t}\n\tif !resp.Success {\n\t\treturn fmt.Errorf(\"plugin %s save stream failed: %s\", d.pluginName, resp.Error)\n\t}\n\treturn nil\n}\n\n// supportsSaveStream reports whether the plugin advertises streaming support.\n// A plugin that predates GetCapabilities returns codes.Unimplemented, which maps to\n// (false, nil) so SaveStream falls back to the buffered Save. Any other error is\n// returned rather than silently downgrading to buffering a potentially large artifact\n// to disk before the real failure would resurface via Save.\nfunc (d *Driver) supportsSaveStream(ctx context.Context) (bool, error) {\n\tresp, err := d.client.GetCapabilities(ctx, &artifact.GetCapabilitiesRequest{})\n\tif err != nil {\n\t\tif status.Code(err) == codes.Unimplemented {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, fmt.Errorf(\"plugin %s get capabilities failed: %w\", d.pluginName, err)\n\t}\n\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})","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/workflow/artifacts/plugin/plugin.go#L295-L331","documentation":"The artifact plugin driver calls the plugin's GetCapabilities RPC (via supportsSaveStream) before SaveStream to check whether the plugin advertises streaming upload support. If the RPC fails with any gRPC status other than Unimplemented (which is deliberately mapped to a silent fallback to buffered Save for old plugins), the driver wraps the error as \"plugin %s get capabilities failed\" and aborts. This is a transport- or server-side failure of the capability probe, not the artifact upload itself.","triggerScenarios":"Calling Driver.SaveStream when the plugin's GetCapabilities RPC returns a non-Unimplemented gRPC error: codes.Unavailable (plugin container not running/ready), codes.DeadlineExceeded (ctx cancelled or plugin too slow), codes.Internal, codes.PermissionDenied, or a connection failure (Uninstantiated: connection refused / no transport).","commonSituations":"Plugin sidecar crashed or was OOM-killed right before the upload; plugin image predates streaming but fails the call for another reason; plugin socket/address misconfigured so the gRPC client can't connect; context deadline too short for a slow plugin startup; plugin returned PermissionDenied due to RBAC/auth changes after a version upgrade.","solutions":["Check the plugin container/pod is running and its logs for a crash (kubectl logs on the plugin sidecar or plugin deployment), then restart it.","Verify the plugin's gRPC address/socket configuration so the client can connect (correct port, unix socket path, service name).","Inspect the wrapped error (%w) for the underlying gRPC status code and address it specifically (e.g. Unavailable = connectivity, DeadlineExceeded = increase timeout/ctx).","If the plugin is an older build that should fall back to buffered Save, ensure it returns codes.Unimplemented for GetCapabilities rather than another error (rebuild/upgrade the plugin against the current artifact plugin API).","Retry the workflow; transient Unavailable errors during plugin rollout resolve once the plugin is healthy."],"exampleFix":"// plugin predating GetCapabilities but crashing with a generic error\n// before: plugin's GetCapabilities returns codes.Internal from a panic\n// after: implement GetCapabilities correctly, or if intentionally unsupported:\nfunc (s *pluginServer) GetCapabilities(ctx context.Context, req *artifact.GetCapabilitiesRequest) (*artifact.GetCapabilitiesResponse, error) {\n\treturn &artifact.GetCapabilitiesResponse{SupportsSaveStream: false}, nil // never return a non-Unimplemented error for 'unsupported'\n}","handlingStrategy":"fallback","validationCode":"// probe plugin health before streaming\nconn, err := grpc.Dial(pluginAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))\nif err != nil { return fmt.Errorf(\"plugin unreachable: %w\", err) }\nclient := artifact.NewArtifactServiceClient(conn)\n_, err = client.GetCapabilities(ctx, &artifact.GetCapabilitiesRequest{})\nif err != nil && status.Code(err) != codes.Unimplemented {\n\treturn fmt.Errorf(\"plugin not ready: %v (code=%s)\", err, status.Code(err))\n}","typeGuard":"func isPluginConnectivityErr(err error) bool {\n\tcode := status.Code(err)\n\treturn code == codes.Unavailable || code == codes.Unimplemented || code == codes.DeadlineExceeded\n}","tryCatchPattern":"ok, err := d.supportsSaveStream(ctx)\nif err != nil {\n\tif isPluginConnectivityErr(err) {\n\t\t// fall back to buffered Save via temp file, or retry after plugin restart\n\t\treturn d.saveStreamViaTempFile(ctx, reader, outputArtifact)\n\t}\n\treturn err\n}","preventionTips":["Add readiness/liveness probes to plugin containers so traffic only reaches healthy plugins.","Set a generous context deadline for the capabilities probe (slow plugin cold starts).","Pin and test plugin images against the artifact plugin API version in CI.","Monitor plugin pod restarts/OOMKills; alert on GetCapabilities failures."],"tags":["grpc","plugin","artifacts","capability-probe"],"backgroundTag":"grpc-unavailable","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"}