microsoft/aspire · error · ArgumentOutOfRangeException

Graceful budget cannot be negative.

Error message

Graceful budget cannot be negative.

What it means

ConsoleCancellationManager.ConfigureForCommand validates the cooperative-shutdown graceful budget and throws ArgumentOutOfRangeException when a negative TimeSpan is supplied. The budget controls how long Ctrl+C lets the AppHost shut down gracefully before escalation, so a negative value is meaningless.

Solutions

  1. Pass a non-negative TimeSpan to ConfigureForCommand.
  2. Clamp or validate the value at the call site before configuring, e.g. TimeSpan.FromMinutes(1) as a default.
  3. If the value is user/config-sourced, validate it is >= TimeSpan.Zero before applying.

Example fix

// before
manager.ConfigureForCommand(TimeSpan.FromSeconds(-5));
// after
var budget = TimeSpan.FromSeconds(Math.Max(0, configuredSeconds));
manager.ConfigureForCommand(budget);
Defensive patterns

Strategy: validation

Validate before calling

if (gracefulBudget < TimeSpan.Zero)
    throw new ArgumentException("Graceful budget must be non-negative.", nameof(gracefulBudget));

Try / catch

try
{
    manager.ConfigureForCommand(budget);
}
catch (ArgumentOutOfRangeException ex) when (ex.ParamName == nameof(gracefulBudget))
{
    logger.LogWarning("Configured graceful budget was negative; falling back to default.");
    manager.ConfigureForCommand(TimeSpan.FromSeconds(30));
}

Prevention

When it happens

Trigger: Calling ConfigureForCommand with a negative TimeSpan (e.g. TimeSpan.FromSeconds(-5)), often from a miscomputed value, a parsed setting that allowed negatives, or a default computed by subtraction.

Common situations: Config-driven timeouts where a negative env/config value was passed through unvalidated; arithmetic like (a - b) producing negative durations; typos in timeout constants.

Related errors


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

Appendix: source

Thrown at src/Aspire.Cli/ConsoleCancellationManager.cs:188

    /// Whether graceful shutdown is enabled for the running command — i.e. a positive budget was
    /// configured via <see cref="ConfigureForCommand"/>. When <see langword="false"/>, shutdown ladders
    /// escalate straight to forceful termination.
    /// </summary>
    public bool IsEnabled => _gracefulBudget > TimeSpan.Zero;

    public bool IsCancellationRequested => _cts.IsCancellationRequested;

    /// <summary>
    /// Sets the graceful-shutdown budget for the currently-executing command. Default is zero, meaning
    /// ladders that consume <see cref="GracefulShutdownToken"/> fall through to escalation immediately
    /// (preserving today's behavior for every command that doesn't opt in). The <c>aspire run</c> handler
    /// calls this so the AppHost gets a real cooperative-shutdown window before escalation.
    /// </summary>
    public void ConfigureForCommand(TimeSpan gracefulBudget)
    {
        if (gracefulBudget < TimeSpan.Zero)
        {
            throw new ArgumentOutOfRangeException(nameof(gracefulBudget), "Graceful budget cannot be negative.");
        }

        _gracefulBudget = gracefulBudget;
    }

    /// <summary>
    /// Starts the graceful-shutdown clock. Idempotent — the first caller arms a <c>CancelAfter(budget)</c>
    /// so <see cref="GracefulShutdownToken"/> is guaranteed to fire within the budget; subsequent calls are
    /// no-ops. Called by whoever initiates teardown (a user signal via <see cref="Cancel"/>, or a child
    /// owner's disposal-driven ladder) so the token is always bounded.
    /// </summary>
    public void BeginGracefulWindow()
    {
        // When a debugger is attached, never arm the clock — the developer needs unlimited time to step
        // through cancellation/cleanup logic. The token therefore never auto-fires; ladders that observe it
        // sit indefinitely (the right behavior for stepping). A manual second Ctrl+C still escalates because
        // it calls Expire() directly, bypassing this method.
        if (Debugger.IsAttached)

View on GitHub (pinned to 25830f84bd)