goharbor/harbor · error · lib/errors.Error
NOT_FOUND
NOT_FOUND
Error message
artifact %s:%s not found
What it means
getByTag resolves a tag reference for the artifact API: it loads the repository, queries tags by RepositoryID and Name, and when zero tags match it returns NotFound with 'artifact repo:tag not found'. It is reached via GetByReference whenever the reference does not parse as a digest — i.e., every GET-artifact call addressed by tag.
Source
Thrown at src/controller/artifact/controller.go:338
return c.assembleArtifact(ctx, art, option), nil
}
func (c *controller) getByTag(ctx context.Context, repository, tag string, option *Option) (*Artifact, error) {
repo, err := c.repoMgr.GetByName(ctx, repository)
if err != nil {
return nil, err
}
tags, err := c.tagCtl.List(ctx, &q.Query{
Keywords: map[string]any{
"RepositoryID": repo.RepositoryID,
"Name": tag,
},
}, nil)
if err != nil {
return nil, err
}
if len(tags) == 0 {
return nil, errors.New(nil).WithCode(errors.NotFoundCode).
WithMessagef("artifact %s:%s not found", repository, tag)
}
return c.Get(ctx, tags[0].ArtifactID, option)
}
func (c *controller) Delete(ctx context.Context, id int64) error {
accs, err := c.accessoryMgr.List(ctx, q.New(q.KeyWords{"ArtifactID": id}))
if err != nil {
return err
}
return orm.WithTransaction(func(ctx context.Context) error {
return c.deleteDeeply(ctx, id, true, len(accs) > 0)
})(orm.SetTransactionOpNameToContext(ctx, "tx-delete-artifact-delete"))
}
// "isRoot" is used to specify whether the artifact is the root parent artifact
// the error handling logic for the root parent artifact and others is different
// "isAccessory" is used to specify whether the artifact is an accessory.View on GitHub (pinned to 7b2fd08cc5)
Solutions
- Confirm the tag exists: list artifacts with tags (GET .../artifacts?with_tag=true) or query the tag API
- Use the digest instead of the tag for stable, race-free lookups
- Double-URL-encode repository names that contain '/'
- Treat 404 as an expected outcome when the tag may have been removed between listing and fetch
Example fix
# before: repository name with '/' not encoded -> wrong repo, 404 curl https://harbor/api/v2.0/projects/library/repositories/my/team/app/artifacts/latest # after: double-URL-encode the repository name curl https://harbor/api/v2.0/projects/library/repositories/my%252Fteam%252Fapp/artifacts/latest
Defensive patterns
Strategy: try-catch
Try / catch
art, err := artifactCtl.GetByReference(ctx, repoName, ref, nil)
if err != nil {
if errors.IsErr(err, errors.NotFoundCode) {
// tag absent: fall back to digest or treat as deleted
}
return err
} Prevention
- Prefer digest references for programmatic lookups
- Double-URL-encode repository names containing '/'
- Handle 404 as expected when tags may be deleted concurrently
When it happens
Trigger: GET /api/v2.0/projects/{project}/repositories/{repo}/artifacts/{tag} where that tag does not exist in that repository: never created, deleted, untagged, mistyped, or the repository name containing '/' was not double-URL-encoded so the wrong repo is looked up.
Common situations: UI or automation refreshing a tag that was deleted concurrently, case mismatches in tag names, forgetting that repo names with slashes need double encoding (my/team/app -> my%252Fteam%252Fapp).
Related errors
- VIOLATE_FOREIGN_KEY_CONSTRAINT
- purge upload age should set with with nh, n is the number of
- invalid json request
- REQUEST_ENTITY_TOO_LARGE
- BAD_REQUEST
AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16).
Data as JSON: /api/errors/af786e2277e9abb4.
Report an issue: GitHub.