{"record":{"id":"82107e372e7bb4b3","repo":"goharbor/harbor","slug":"not-found-82107e","errorCode":"NOT_FOUND","errorMessage":"no blob found to mark delete failed, ID:%d, digest:%s","messagePattern":"no blob found to mark delete failed, ID:(.+?), digest:(.+?)","errorType":"error_code","errorClass":"lib/errors.Error","httpStatus":null,"severity":"error","filePath":"src/jobservice/job/impl/gc/garbage_collection.go","lineNumber":728,"sourceCode":"\t\t\t\treturn blobs, err\n\t\t\t}\n\t\t\tblobs = append(blobs, artBlobs...)\n\t\t}\n\t}\n\n\treturn blobs, err\n}\n\n// markDeleteFailed set the blob status to StatusDeleteFailed\nfunc (gc *GarbageCollector) markDeleteFailed(ctx job.Context, blob *blobModels.Blob) error {\n\tblob.Status = blobModels.StatusDeleteFailed\n\tcount, err := gc.blobMgr.UpdateBlobStatus(ctx.SystemContext(), blob)\n\tif err != nil {\n\t\tgc.logger.Errorf(\"failed to mark gc candidate delete failed: %s, %s\", blob.Digest, blob.Status)\n\t\treturn errors.Wrapf(err, \"failed to mark gc candidate delete failed: %s, %s\", blob.Digest, blob.Status)\n\t}\n\tif count == 0 {\n\t\treturn errors.New(nil).WithMessagef(\"no blob found to mark delete failed, ID:%d, digest:%s\", blob.ID, blob.Digest).WithCode(errors.NotFoundCode)\n\t}\n\treturn nil\n}\n\nfunc (gc *GarbageCollector) shouldStop(ctx job.Context) bool {\n\topCmd, exit := ctx.OPCommand()\n\tif exit && opCmd.IsStop() {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc saveGCRes(ctx job.Context, sweepSize, blobs, manifests int64) error {\n\tgcObj := struct {\n\t\tSweepSize int64 `json:\"freed_space\"`\n\t\tBlobs     int64 `json:\"purged_blobs\"`\n\t\tManifests int64 `json:\"purged_manifests\"`\n\t}{","sourceCodeStart":710,"sourceCodeEnd":746,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/jobservice/job/impl/gc/garbage_collection.go#L710-L746","documentation":"During garbage collection, when deleting a blob fails, GarbageCollector.markDeleteFailed (src/jobservice/job/impl/gc/garbage_collection.go:728) flags the blob StatusDeleteFailed via blobMgr.UpdateBlobStatus, which returns the count of updated rows. A count of 0 means no row matched the blob's ID/digest, so a NotFound-coded error is returned: the database row vanished between candidate selection and the status update. The usual cause is a concurrent GC or an external process deleting the blob row first.","triggerScenarios":"Two GC executions overlapping on the same database (manual gcctl run during the scheduled GC, or a bypassed distributed lock); an external script/DBA deleting blob rows while GC holds stale candidates; stale references after restoring the DB from backup.","commonSituations":"Running offline GC while online GC is scheduled; multiple Harbor instances sharing one database; manual database surgery on the blob table.","solutions":["Serialize GC executions — let Harbor's GC lock order scheduled and manual runs instead of bypassing it","Treat the error as usually benign: the blob is already gone; rerun GC and the candidate list will no longer include it","Find and stop whatever else deletes blob rows (second instance, cron job, manual DELETE)","If it persists, query the blob table for the reported ID/digest to confirm the row is gone"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := gc.markDeleteFailed(ctx, blob); err != nil {\n    if liberrors.IsNotFound(err) { // UpdateBlobStatus affected 0 rows\n        gc.logger.Warningf(\"blob %d/%s already removed by a concurrent run; skipping\", blob.ID, blob.Digest)\n        continue\n    }\n    return err\n}","preventionTips":["Run exactly one GC at a time — rely on Harbor's GC lock and avoid direct gcctl/DB access during scheduled GC","Do not manually DELETE blob rows while GC is executing","Alert on overlapping GC schedules so the race is fixed, not just tolerated"],"tags":["gc","blob","database","concurrency","not-found","harbor","jobservice"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}