goharbor/harbor · error

the tag deletion isn't supported

Error message

the tag deletion isn't supported

What it means

Returned unconditionally by (*native.Adapter).DeleteTag (src/pkg/reg/adapter/native/adapter.go:245), the adapter for plain Docker Registry v2 endpoints. The Docker Distribution HTTP API has no per-tag delete endpoint (tags are mutable pointers to manifests), so the adapter declares tag deletion unsupported rather than silently doing nothing. Note the sibling DeleteManifest/CanBeMount comments: only manifest/digest operations exist for docker registry.

Source

Thrown at src/pkg/reg/adapter/native/adapter.go:245

	return filter.DoFilterArtifacts(artifacts, filters)
}

// PingSimple checks whether the registry is available. It checks the connectivity and certificate (if TLS enabled)
// only, regardless of 401/403 error.
func (a *Adapter) PingSimple() error {
	err := a.Ping()
	if err == nil {
		return nil
	}
	if errors.IsErr(err, errors.UnAuthorizedCode) || errors.IsErr(err, errors.ForbiddenCode) {
		return nil
	}
	return err
}

// DeleteTag isn't supported for docker registry
func (a *Adapter) DeleteTag(_, _ string) error {
	return errors.New("the tag deletion isn't supported")
}

// CanBeMount isn't supported for docker registry
func (a *Adapter) CanBeMount(_ string) (mount bool, repository string, err error) {
	return false, "", nil
}

View on GitHub (pinned to 7b2fd08cc5)

Solutions

  1. Delete by digest instead: resolve the tag to a manifest digest and call DeleteManifest (or the registry DELETE /v2/<name>/manifests/<digest> API).
  2. In Harbor, delete the artifact (which removes all its tags) via the Harbor API rather than the registry adapter.
  3. Check the adapter's Info()/capabilities before attempting tag-level operations and skip unsupported ones.
  4. If per-tag removal is required, run a garbage-collect-enabled retention policy (Harbor retention rules / registry GC) instead of tag deletion.

Example fix

// before
err := adapter.DeleteTag("myrepo", "v1.0")
// always: the tag deletion isn't supported

// after
manifest, err := adapter.ListTagExist... // resolve digest, e.g. via HEAD /v2/myrepo/manifests/v1.0
digest := manifest.Digest
err := adapter.DeleteManifest("myrepo", digest.String())
Defensive patterns

Strategy: validation

Validate before calling

// docker registry (native) has no tag-delete API: gate the call
info, err := adapter.Info()
if err != nil {
	return err
}
if strings.Contains(adapter.Info().Name, "docker") {
	// skip DeleteTag; use digest-based deletion
}
_ = info

Type guard

func supportsTagDeletion(a adp.Adapter) bool {
	// native.Adapter is the docker registry v2 adapter and rejects DeleteTag
	_, isNative := a.(*native.Adapter)
	return !isNative
}

Try / catch

if err := adapter.DeleteTag(repo, tag); err != nil {
	if strings.Contains(err.Error(), "tag deletion isn't supported") {
		// fall back: resolve tag -> digest, then DeleteManifest(repo, digest)
		return adapter.DeleteManifest(repo, digest)
	}
	return err
}

Prevention

When it happens

Trigger: Invoking adapter.DeleteTag(repository, tag) on the native adapter — directly or via a replication/rule engine that calls tag deletion on a docker-registry-type destination. The method signature accepts any strings but always returns this error.

Common situations: Porting logic from Harbor (which supports tag delete via its own artifact model) to a raw docker registry destination; retention/cleanup scripts that try to delete tags on a Distribution registry; version changes where a previously worked-around path now funnels into DeleteTag.

Related errors


AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16). Data as JSON: /api/errors/480507d1f5560e06. Report an issue: GitHub.