{"record":{"id":"cfe450468cce7090","repo":"microsoft/aspire","slug":"ago-must-be-at-least-1-minute-to-be-compatible-with-acr","errorCode":null,"errorMessage":"Ago must be at least 1 minute to be compatible with acr purge.","messagePattern":"Ago must be at least 1 minute to be compatible with acr purge\\.","errorType":"validation","errorClass":"ArgumentOutOfRangeException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryExtensions.cs","lineNumber":330,"sourceCode":"            \"\"\".ReplaceLineEndings(\"\\n\");\n    }\n\n    /// <summary>\n    /// Formats a <see cref=\"TimeSpan\"/> into a Go-style duration string compatible with <c>acr purge --ago</c>.\n    /// Valid units: <c>d</c> (days), <c>h</c> (hours), <c>m</c> (minutes).\n    /// </summary>\n    /// <remarks>\n    /// From the docs: https://learn.microsoft.com/azure/container-registry/container-registry-auto-purge#example-scheduled-purge-of-multiple-repositories-in-a-registry\n    ///   A Go-style duration string to indicate a duration beyond which images are deleted. The duration consists of a sequence\n    ///   of one or more decimal numbers, each with a unit suffix. Valid time units include \"d\" for days, \"h\" for hours, and \"m\"\n    ///   for minutes. For example, --ago 2d3h6m selects all filtered images last modified more than two days, 3 hours, and 6 minutes\n    ///   ago, and --ago 1.5h selects images last modified more than 1.5 hours ago.\n    /// </remarks>\n    private static string FormatAgo(TimeSpan ago)\n    {\n        if (ago.TotalMinutes < 1 && ago != TimeSpan.Zero)\n        {\n            throw new ArgumentOutOfRangeException(nameof(ago), ago, \"Ago must be at least 1 minute to be compatible with acr purge.\");\n        }\n\n        if (ago == TimeSpan.Zero)\n        {\n            return \"0d\";\n        }\n\n        var sb = new StringBuilder();\n\n        if (ago.Days > 0)\n        {\n            sb.Append(CultureInfo.InvariantCulture, $\"{ago.Days}d\");\n        }\n\n        if (ago.Hours > 0)\n        {\n            sb.Append(CultureInfo.InvariantCulture, $\"{ago.Hours}h\");\n        }","sourceCodeStart":312,"sourceCodeEnd":348,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryExtensions.cs#L312-L348","documentation":"FormatAgo converts a TimeSpan into the --ago flag value for the Azure 'acr purge' command, which quantizes age into units no smaller than a minute. The library throws ArgumentOutOfRangeException when given a nonzero TimeSpan smaller than one minute because acr purge cannot express such a threshold.","triggerScenarios":"Calling the ACR purge/purge-images helper (via purgeAgo) with a TimeSpan whose TotalMinutes < 1 and which is not exactly TimeSpan.Zero, e.g. TimeSpan.FromSeconds(30) or a computed 'now - lastModified' gap under a minute.","commonSituations":"Configuring a retention/purge policy with a very short maxAge like 30 seconds; a test that builds an ago value from a recent timestamp so the delta is sub-minute; misreading '--ago' units as seconds instead of minutes/hours.","solutions":["Pass a TimeSpan of at least one minute (e.g. TimeSpan.FromMinutes(1) or larger such as FromHours(24)).","If you intend 'purge everything regardless of age', pass TimeSpan.Zero explicitly, which formats as '0d'.","Clamp the value before calling: if the computed TimeSpan is under one minute, raise it to one minute or skip the purge.","If you need sub-minute retention semantics, do the deletion directly with the Azure SDK (container registry client) instead of acr purge."],"exampleFix":"// before\nvar ago = TimeSpan.FromSeconds(45); // throws\nPurgeImages(registry, ago);\n// after\nvar ago = TimeSpan.FromMinutes(1); // minimum acr purge supports\nPurgeImages(registry, ago);","handlingStrategy":"validation","validationCode":"if (ago != TimeSpan.Zero && ago.TotalMinutes < 1)\n{\n    throw new ArgumentOutOfRangeException(nameof(ago), \"Ago must be at least 1 minute for acr purge; use TimeSpan.Zero for no age filter.\");\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    PurgeImages(registry, ago);\n}\ncatch (ArgumentOutOfRangeException ex)\n{\n    logger.LogWarning(ex, \"Purge ago {Ago} below acr purge minimum; clamping to 1 minute.\", ago);\n    ago = TimeSpan.FromMinutes(1);\n}","preventionTips":["Treat one minute as the minimum quantum for acr purge age values.","Use TimeSpan.Zero explicitly for 'no age restriction' instead of tiny durations.","Unit-test age computation helpers to assert >= 1 minute output for nonzero values."],"tags":["argument-out-of-range","azure","container-registry","timespan"],"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"}