{"record":{"id":"4742b21e8bcfcff0","repo":"goharbor/harbor","slug":"precondition-4742b2","errorCode":"PRECONDITION","errorMessage":"the tag %s configured as immutable, cannot be updated","messagePattern":"the tag (.+?) configured as immutable, cannot be updated","errorType":"error_code","errorClass":"github.com/goharbor/harbor/src/lib/errors.Error","httpStatus":412,"severity":"error","filePath":"src/controller/tag/controller.go","lineNumber":103,"sourceCode":"\t\t\t\"name\":          name,\n\t\t},\n\t}\n\ttags, err := c.List(ctx, query, &Option{\n\t\tWithImmutableStatus: true,\n\t})\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\t// the tag already exists under the repository\n\tif len(tags) > 0 {\n\t\ttag := tags[0]\n\t\t// the tag already exists under the repository and is attached to the artifact, return directly\n\t\tif tag.ArtifactID == artifactID {\n\t\t\treturn tag.ID, nil\n\t\t}\n\t\t// existing tag must check the immutable status and signature\n\t\tif tag.Immutable {\n\t\t\treturn 0, errors.New(nil).WithCode(errors.PreconditionCode).\n\t\t\t\tWithMessagef(\"the tag %s configured as immutable, cannot be updated\", tag.Name)\n\t\t}\n\t\t// the tag exists under the repository, but it is attached to other artifact\n\t\t// update it to point to the provided artifact\n\t\ttag.ArtifactID = artifactID\n\t\ttag.PushTime = time.Now()\n\t\tif err := c.Update(ctx, tag, \"ArtifactID\", \"PushTime\"); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tc.touchRepo(ctx, repositoryID)\n\t\treturn tag.ID, nil\n\t}\n\n\t// the tag doesn't exist under the repository, create it\n\t// use orm.WithTransaction here to avoid the issue:\n\t// https://www.postgresql.org/message-id/002e01c04da9%24a8f95c20%2425efe6c1%40lasting.ro\n\ttagID := int64(0)\n\tif err = orm.WithTransaction(func(ctx context.Context) error {","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/controller/tag/controller.go#L85-L121","documentation":"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).","triggerScenarios":"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.","commonSituations":"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. '**').","solutions":["Push under a new/unique tag (timestamped or git-sha tags)","Review and adjust the project's immutable tag rules (Project -> Configuration -> Immutable Tags), e.g. narrow '**' to specific patterns","If the re-point is intentional and approved, temporarily remove the rule, re-push, re-add the rule"],"exampleFix":"# before\nimmutable rule: releases/**\ndocker push myharbor.com/prod/app:releases/v1   # retag -> 412\n\n# after\n# option 1: unique tag\ndocker push myharbor.com/prod/app:releases/v1-a1b2c3d\n# option 2: narrow the rule to \"releases/**-final\" so CI tags stay mutable","handlingStrategy":"try-catch","validationCode":"tags, err := tagCtl.List(ctx, &q.Query{Keywords: map[string]any{\"RepositoryID\": repoID, \"Name\": name}}, &Option{WithImmutableStatus: true})\nif err == nil && len(tags) > 0 && tags[0].Immutable && tags[0].ArtifactID != artifactID {\n    return fmt.Errorf(\"tag %s immutable; choose another tag\", name)\n}","typeGuard":"func isImmutableTagErr(err error) bool {\n    return errors.IsErr(err, errors.PreconditionCode) &&\n        strings.Contains(err.Error(), \"immutable\")\n}","tryCatchPattern":"if _, err := tagCtl.Create(ctx, repoID, artifactID, &model.Tag{Name: name}); err != nil {\n    if errors.IsErr(err, errors.PreconditionCode) && strings.Contains(err.Error(), \"immutable, cannot be updated\") {\n        // push under a unique tag instead of re-pointing the immutable one\n    }\n    return err\n}","preventionTips":["Use unique (sha/date) tags for CI pushes; reserve mutable names like 'latest' outside immutable patterns","Review immutable tag rules before adding new push targets to a pipeline","Pre-list tags with immutable status when automation must re-point existing names"],"tags":["harbor","tag","immutability","registry-push","precondition"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}