goharbor/harbor · error · github.com/goharbor/harbor/src/lib/errors.Error

PRECONDITION

PRECONDITION

Error message

the tag %s configured as immutable, cannot be updated

What it means

The tag controller's creation path (src/controller/tag/controller.go:103) finds the tag already exists in the repository but is attached to a different artifact. Before re-pointing it, it checks immutability; if a project immutable-tag rule matches, the update is refused with PRECONDITION (HTTP 412).

Source

Thrown at src/controller/tag/controller.go:103

			"name":          name,
		},
	}
	tags, err := c.List(ctx, query, &Option{
		WithImmutableStatus: true,
	})
	if err != nil {
		return 0, err
	}
	// the tag already exists under the repository
	if len(tags) > 0 {
		tag := tags[0]
		// the tag already exists under the repository and is attached to the artifact, return directly
		if tag.ArtifactID == artifactID {
			return tag.ID, nil
		}
		// existing tag must check the immutable status and signature
		if tag.Immutable {
			return 0, errors.New(nil).WithCode(errors.PreconditionCode).
				WithMessagef("the tag %s configured as immutable, cannot be updated", tag.Name)
		}
		// the tag exists under the repository, but it is attached to other artifact
		// update it to point to the provided artifact
		tag.ArtifactID = artifactID
		tag.PushTime = time.Now()
		if err := c.Update(ctx, tag, "ArtifactID", "PushTime"); err != nil {
			return 0, err
		}
		c.touchRepo(ctx, repositoryID)
		return tag.ID, nil
	}

	// the tag doesn't exist under the repository, create it
	// use orm.WithTransaction here to avoid the issue:
	// https://www.postgresql.org/message-id/002e01c04da9%24a8f95c20%2425efe6c1%40lasting.ro
	tagID := int64(0)
	if err = orm.WithTransaction(func(ctx context.Context) error {

View on GitHub (pinned to 7b2fd08cc5)

Solutions

  1. Push under a new/unique tag (timestamped or git-sha tags)
  2. Review and adjust the project's immutable tag rules (Project -> Configuration -> Immutable Tags), e.g. narrow '**' to specific patterns
  3. If the re-point is intentional and approved, temporarily remove the rule, re-push, re-add the rule

Example fix

# before
immutable rule: releases/**
docker push myharbor.com/prod/app:releases/v1   # retag -> 412

# after
# option 1: unique tag
docker push myharbor.com/prod/app:releases/v1-a1b2c3d
# option 2: narrow the rule to "releases/**-final" so CI tags stay mutable
Defensive patterns

Strategy: try-catch

Validate before calling

tags, err := tagCtl.List(ctx, &q.Query{Keywords: map[string]any{"RepositoryID": repoID, "Name": name}}, &Option{WithImmutableStatus: true})
if err == nil && len(tags) > 0 && tags[0].Immutable && tags[0].ArtifactID != artifactID {
    return fmt.Errorf("tag %s immutable; choose another tag", name)
}

Type guard

func isImmutableTagErr(err error) bool {
    return errors.IsErr(err, errors.PreconditionCode) &&
        strings.Contains(err.Error(), "immutable")
}

Try / catch

if _, err := tagCtl.Create(ctx, repoID, artifactID, &model.Tag{Name: name}); err != nil {
    if errors.IsErr(err, errors.PreconditionCode) && strings.Contains(err.Error(), "immutable, cannot be updated") {
        // push under a unique tag instead of re-pointing the immutable one
    }
    return err
}

Prevention

When it happens

Trigger: Pushing an image that re-points an existing tag (e.g. 'latest') which is protected by a project immutable tag rule; POST /api/v2.0/projects/{p}/repositories/{r}/artifacts/{digest}/tags with a name already bound to another artifact and covered by an immutable rule.

Common situations: CI pipeline pushing a new build to 'stable'/'release' tags that were later made immutable; immutable rule added after the tag existed, blocking subsequent pushes; default 'latest' flows in projects with wildcard immutable rules (e.g. '**').

Related errors


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