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
- Pass a non-negative TimeSpan to ConfigureForCommand.
- Clamp or validate the value at the call site before configuring, e.g. TimeSpan.FromMinutes(1) as a default.
- 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
- Clamp parsed timeout values with Math.Max(TimeSpan.Zero, value).
- Validate config/env-sourced durations before applying them.
- Avoid deriving budgets from unchecked subtractions.
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
- {cancellationMessage}
- {cancellationMessage}
- Address prefix must be a string or a parameter resource…
- Address prefix must be omitted, a string, or a parameter…
- All resources should be of the same kind when calling…
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)