argoproj/argo-workflows · error

plugin %s delete failed: %s

Error message

plugin %s delete failed: %s

What it means

After a successful Delete RPC, the plugin reports its own logical outcome via resp.Success and an error string. When Success is false, the driver raises "plugin %s delete failed: %s" with the plugin-provided message. The gRPC call worked; the plugin (or its backing storage) refused or failed the delete.

Source

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

// saveStreamViaTempFile is the fallback used when the plugin doesn't implement
// streaming SaveStream: buffer to a temp file and call the existing unary Save.
func (d *Driver) saveStreamViaTempFile(ctx context.Context, reader io.Reader, outputArtifact *wfv1.Artifact) error {
	return common.SaveStreamViaTempFile(reader, "plugin-upload-*", func(path string) error {
		return d.Save(ctx, path, outputArtifact)
	})
}

// Delete implements ArtifactDriver.Delete by calling the plugin service
func (d *Driver) Delete(ctx context.Context, artifactRef *wfv1.Artifact) error {
	grpcArtifact := convertToGRPC(artifactRef)
	resp, err := d.client.Delete(ctx, &artifact.DeleteArtifactRequest{
		Artifact: grpcArtifact,
	})
	if err != nil {
		return fmt.Errorf("plugin %s delete failed: %w", d.pluginName, err)
	}
	if !resp.Success {
		return fmt.Errorf("plugin %s delete failed: %s", d.pluginName, resp.Error)
	}
	return nil
}

// ListObjects implements ArtifactDriver.ListObjects by calling the plugin service
func (d *Driver) ListObjects(ctx context.Context, artifactRef *wfv1.Artifact) ([]string, error) {
	grpcArtifact := convertToGRPC(artifactRef)
	resp, err := d.client.ListObjects(ctx, &artifact.ListObjectsRequest{
		Artifact: grpcArtifact,
	})
	if err != nil {
		return nil, fmt.Errorf("plugin %s list objects failed: %w", d.pluginName, err)
	}
	if resp.Error != "" {
		return nil, fmt.Errorf("plugin %s list objects failed: %s", d.pluginName, resp.Error)
	}
	return resp.Objects, nil
}

View on GitHub (pinned to 35bff19146)

Solutions

  1. Read the %s message from the plugin — it names the storage-level cause (e.g. NoSuchKey, AccessDenied) and fix accordingly.
  2. If the object is already gone, treat the delete as successful; many plugins report failure for missing keys — check idempotency of your cleanup logic.
  3. Verify the plugin's storage credentials and bucket/container configuration are current.
  4. Ensure the artifact was created by the same plugin/backend currently configured; reconfigure or delete via the original backend.

Example fix

// caller treating already-deleted as success
// before:
err := driver.Delete(ctx, art)
// after:
if err := driver.Delete(ctx, art); err != nil {
	if strings.Contains(err.Error(), "NoSuchKey") || strings.Contains(err.Error(), "not found") {
		return nil // idempotent delete
	}
	return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// only delete artifacts known to exist (track provenance)
if art == nil || art.GetName() == "" || art.GetArtifactGC().GetStrategy() == wfv1.ArtifactGCNever {
	return nil // nothing to delete / deletion disabled
}

Type guard

func isPluginLogicalDeleteFailure(err error) bool {
	// error 512 has no gRPC status (RPC succeeded); detect by absence of a status code
	return err != nil && status.Code(err) == codes.Unknown && strings.Contains(err.Error(), "delete failed")
}

Try / catch

if err := driver.Delete(ctx, art); err != nil {
	var msg string
	if isPluginLogicalDeleteFailure(err) {
		msg = strings.TrimPrefix(err.Error(), "plugin "+pluginName+" delete failed: ")
	}
	switch {
	case strings.Contains(msg, "not found"), strings.Contains(msg, "NoSuchKey"):
		return nil // already gone: idempotent success
	default:
		return err
	}
}

Prevention

When it happens

Trigger: Calling Driver.Delete where the plugin responds Success=false, e.g. the artifact key does not exist in the plugin's storage backend, the plugin lacks permissions on the object, or the backend returned a storage-level error that the plugin mapped into the response's Error field.

Common situations: Retrying deletion of an already-deleted artifact; misconfigured plugin storage credentials/region so the backend denies the operation; artifact key from a different plugin/backend than the one now configured (switched plugin config mid-lifecycle).

Related errors


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