goharbor/harbor · error

lack digest of candidate for

Error message

lack digest of candidate for %s/%s

What it means

Returned by the retention client's GetCandidates when an artifact fetched from Harbor core has an empty Digest field. Retention deletion requires a digest to identify the artifact, so a digest-less artifact for the named namespace/repository aborts candidate collection. It indicates inconsistent or malformed artifact data returned by the core API.

Solutions

  1. Inspect the artifact record for the named repository in Harbor's database
  2. Re-index or repair the artifact so its manifest digest is populated
  3. Check for a core API version mismatch returning incomplete artifacts
  4. Skip digest-less artifacts only if the retention policy allows it
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at src/pkg/retention/dep/client.go:116 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at src/pkg/retention/dep/client.go:116

	internalCoreURL string
	coreClient      core.Client
}

// GetCandidates gets the tag candidates under the repository
func (bc *basicClient) GetCandidates(repository *selector.Repository) ([]*selector.Candidate, error) {
	if repository == nil {
		return nil, errors.New("repository is nil")
	}
	candidates := make([]*selector.Candidate, 0)
	switch repository.Kind {
	case selector.Image:
		artifacts, err := bc.coreClient.ListAllArtifacts(repository.Namespace, repository.Name)
		if err != nil {
			return nil, err
		}
		for _, art := range artifacts {
			if art.Digest == "" {
				return nil, fmt.Errorf("lack digest of candidate for %s/%s", repository.Namespace, repository.Name)
			}
			labels := make([]string, 0)
			for _, label := range art.Labels {
				labels = append(labels, label.Name)
			}
			tags := make([]string, 0)
			lastPulledTime := art.PullTime
			lastPushedTime := art.PushTime
			for _, t := range art.Tags {
				tags = append(tags, t.Name)
				if t.PullTime.After(lastPulledTime) {
					lastPulledTime = t.PullTime
				}
				if t.PushTime.After(lastPushedTime) {
					lastPushedTime = t.PushTime
				}
			}
			candidate := &selector.Candidate{

View on GitHub (pinned to 7b2fd08cc5)