microsoft/aspire · error · ArgumentOutOfRangeException

Ago must be at least 1 minute to be compatible with acr…

Error message

Ago must be at least 1 minute to be compatible with acr purge.

What it means

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.

Solutions

  1. Pass a TimeSpan of at least one minute (e.g. TimeSpan.FromMinutes(1) or larger such as FromHours(24)).
  2. If you intend 'purge everything regardless of age', pass TimeSpan.Zero explicitly, which formats as '0d'.
  3. Clamp the value before calling: if the computed TimeSpan is under one minute, raise it to one minute or skip the purge.
  4. If you need sub-minute retention semantics, do the deletion directly with the Azure SDK (container registry client) instead of acr purge.

Example fix

// before
var ago = TimeSpan.FromSeconds(45); // throws
PurgeImages(registry, ago);
// after
var ago = TimeSpan.FromMinutes(1); // minimum acr purge supports
PurgeImages(registry, ago);
Defensive patterns

Strategy: validation

Validate before calling

if (ago != TimeSpan.Zero && ago.TotalMinutes < 1)
{
    throw new ArgumentOutOfRangeException(nameof(ago), "Ago must be at least 1 minute for acr purge; use TimeSpan.Zero for no age filter.");
}

Try / catch

try
{
    PurgeImages(registry, ago);
}
catch (ArgumentOutOfRangeException ex)
{
    logger.LogWarning(ex, "Purge ago {Ago} below acr purge minimum; clamping to 1 minute.", ago);
    ago = TimeSpan.FromMinutes(1);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/cfe450468cce7090. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryExtensions.cs:330

            """.ReplaceLineEndings("\n");
    }

    /// <summary>
    /// Formats a <see cref="TimeSpan"/> into a Go-style duration string compatible with <c>acr purge --ago</c>.
    /// Valid units: <c>d</c> (days), <c>h</c> (hours), <c>m</c> (minutes).
    /// </summary>
    /// <remarks>
    /// From the docs: https://learn.microsoft.com/azure/container-registry/container-registry-auto-purge#example-scheduled-purge-of-multiple-repositories-in-a-registry
    ///   A Go-style duration string to indicate a duration beyond which images are deleted. The duration consists of a sequence
    ///   of one or more decimal numbers, each with a unit suffix. Valid time units include "d" for days, "h" for hours, and "m"
    ///   for minutes. For example, --ago 2d3h6m selects all filtered images last modified more than two days, 3 hours, and 6 minutes
    ///   ago, and --ago 1.5h selects images last modified more than 1.5 hours ago.
    /// </remarks>
    private static string FormatAgo(TimeSpan ago)
    {
        if (ago.TotalMinutes < 1 && ago != TimeSpan.Zero)
        {
            throw new ArgumentOutOfRangeException(nameof(ago), ago, "Ago must be at least 1 minute to be compatible with acr purge.");
        }

        if (ago == TimeSpan.Zero)
        {
            return "0d";
        }

        var sb = new StringBuilder();

        if (ago.Days > 0)
        {
            sb.Append(CultureInfo.InvariantCulture, $"{ago.Days}d");
        }

        if (ago.Hours > 0)
        {
            sb.Append(CultureInfo.InvariantCulture, $"{ago.Hours}h");
        }

View on GitHub (pinned to 25830f84bd)