{"record":{"id":"35d1ca315ebba1f1","repo":"goharbor/harbor","slug":"not-found-35d1ca","errorCode":"NOT_FOUND","errorMessage":"no blob item is updated to StatusNone, id:%d, digest:%s","messagePattern":"no blob item is updated to StatusNone, id:(.+?), digest:(.+?)","errorType":"http","errorClass":"lib/errors.Error","httpStatus":404,"severity":"error","filePath":"src/controller/blob/controller.go","lineNumber":366,"sourceCode":"\tif err != nil {\n\t\tif err == redis.Nil {\n\t\t\treturn 0, nil\n\t\t}\n\n\t\treturn 0, err\n\t}\n\n\treturn size, nil\n}\n\nfunc (c *controller) Touch(ctx context.Context, blob *blob.Blob) error {\n\tblob.Status = blob_models.StatusNone\n\tcount, err := c.blobMgr.UpdateBlobStatus(ctx, blob)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif count == 0 {\n\t\treturn errors.New(nil).WithMessagef(\"no blob item is updated to StatusNone, id:%d, digest:%s\", blob.ID, blob.Digest).WithCode(errors.NotFoundCode)\n\t}\n\treturn nil\n}\n\nfunc (c *controller) Fail(ctx context.Context, blob *blob.Blob) error {\n\tblob.Status = blob_models.StatusDeleteFailed\n\tcount, err := c.blobMgr.UpdateBlobStatus(ctx, blob)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif count == 0 {\n\t\treturn errors.New(nil).WithMessagef(\"no blob item is updated to StatusDeleteFailed, id:%d, digest:%s\", blob.ID, blob.Digest).WithCode(errors.NotFoundCode)\n\t}\n\treturn nil\n}\n\nfunc (c *controller) Update(ctx context.Context, blob *blob.Blob) error {\n\treturn c.blobMgr.Update(ctx, blob)","sourceCodeStart":348,"sourceCodeEnd":384,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/controller/blob/controller.go#L348-L384","documentation":"Touch() sets blob.Status = StatusNone and issues an UPDATE via UpdateBlobStatus. The manager returns the number of affected rows; when that count is 0 (no row matches the blob's ID/digest/status transition) Harbor returns NOT_FOUND including the blob's id and digest. It means the state-machine transition 'to StatusNone' matched nothing - almost always a concurrently deleted or already-removed blob.","triggerScenarios":"GC or artifact-deletion flow calls Touch for a blob that another concurrent GC run already deleted; a retried job touches a blob whose row was removed between the earlier List and this UPDATE; the DB was manually pruned.","commonSituations":"Overlapping GC executions, retry of a failed delete job after cleanup completed, long-running jobs holding stale blob references.","solutions":["Re-list the blob by digest; if it no longer exists, treat Touch as success (idempotent no-op) and continue the sweep.","Ensure only one GC execution runs at a time (Harbor enforces this for its own GC; make custom jobs respect it too).","If the row still exists, reload it and check whether its status already advanced, then retry the transition."],"exampleFix":"// before\nif err := c.Touch(ctx, b); err != nil { return err }\n\n// after\nif err := c.Touch(ctx, b); err != nil {\n    if liberrors.IsErr(err, liberrors.NotFoundCode) {\n        // already removed by a concurrent run - not an error\n        return nil\n    }\n    return err\n}","handlingStrategy":"fallback","validationCode":"// before Touch, confirm the blob still exists:\nexists, err := blobCtl.Exists(ctx, b.Digest)\nif err == nil && !exists { return nil /* nothing to touch */ }","typeGuard":null,"tryCatchPattern":"if err := c.Touch(ctx, b); err != nil {\n    if liberrors.IsErr(err, liberrors.NotFoundCode) {\n        // row gone (concurrent delete): treat as done, keep sweeping\n        return nil\n    }\n    return err\n}","preventionTips":["Make blob-state transitions idempotent; NotFound on Touch means already removed.","Serialize GC executions so blobs are not deleted under an active sweep.","Log blob id+digest on this path to identify overlapping cleaners."],"tags":["blob","garbage-collection","concurrency","not-found","idempotency"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}