{"record":{"id":"cf32d8992810e2f1","repo":"microsoft/aspire","slug":"keep-must-be-greater-than-zero","errorCode":null,"errorMessage":"Keep must be greater than zero.","messagePattern":"Keep must be greater than zero\\.","errorType":"validation","errorClass":"ArgumentOutOfRangeException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryExtensions.cs","lineNumber":180,"sourceCode":"        ArgumentNullException.ThrowIfNull(builder);\n        ArgumentException.ThrowIfNullOrWhiteSpace(schedule);\n        schedule = schedule.Trim();\n\n        try\n        {\n            _ = CrontabSchedule.Parse(schedule, new CrontabSchedule.ParseOptions { IncludingSeconds = false });\n        }\n        catch (CrontabException ex)\n        {\n            throw new ArgumentException(\n                $\"The schedule '{schedule}' is not a valid five-part cron expression (minute hour day-of-month month day-of-week). {ex.Message}\",\n                nameof(schedule),\n                ex);\n        }\n\n        if (keep < 1)\n        {\n            throw new ArgumentOutOfRangeException(nameof(keep), keep, \"Keep must be greater than zero.\");\n        }\n\n        var purgeAgo = FormatAgo(ago ?? TimeSpan.Zero);\n\n        return builder.ConfigureInfrastructure(infra =>\n        {\n            var prefix = \"purgeOldImages\";\n\n            var registry = infra.GetProvisionableResources().OfType<ContainerRegistryService>().Single();\n            var allTasks = infra.GetProvisionableResources().OfType<ContainerRegistryTask>()\n                .Where(t => t.Parent == registry)\n                .ToList();\n            var autoNamedTasks = allTasks\n                .Where(t => t.Name.Value?.StartsWith(prefix, StringComparison.Ordinal) == true)\n                .ToList();\n            var taskIndex = autoNamedTasks.Count;\n\n            if (!string.IsNullOrWhiteSpace(taskName))","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryExtensions.cs#L162-L198","documentation":"WithPurgeTask's keep parameter controls how many images a purge retains, and values below 1 make no sense (nothing or negative retention). The method throws ArgumentOutOfRangeException with the actual keep value when keep < 1.","triggerScenarios":"Calling WithPurgeTask with keep set to 0 or a negative number, or with a computed value that underflows to <= 0.","commonSituations":"Configuring 'delete everything' by passing 0; a config value or calculation producing a negative count; swapping parameter order so a limit lands in keep.","solutions":["Pass keep >= 1 (at least 1 image retained)","Clamp computed values: Math.Max(1, computedKeep)","Fix configuration so the keep setting holds a positive integer"],"exampleFix":"// before\n.WithPurgeTask(schedule: \"0 3 * * *\", keep: 0)\n// after\n.WithPurgeTask(schedule: \"0 3 * * *\", keep: 5)","handlingStrategy":"validation","validationCode":"if (keep < 1) throw new ArgumentOutOfRangeException(nameof(keep), keep, \"Keep must be >= 1.\");","typeGuard":null,"tryCatchPattern":"try { env.WithPurgeTask(schedule: cron, keep: keep); }\ncatch (ArgumentOutOfRangeException ex) when (ex.ParamName == \"keep\") { log.LogError(ex, \"Invalid keep value\"); }","preventionTips":["Clamp computed retention values with Math.Max(1, value)","Validate config integers before passing them in","Never encode 'delete all' as keep=0; it is unsupported"],"tags":["azure","container-registry","argument-out-of-range","purge"],"backgroundTag":"argument-out-of-range","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}