dotnet/efcore · error · InvalidOperationException
The specified 'CommandTimeout' value
Error message
The specified 'CommandTimeout' value '{value}' is not valid. It must be a positive number. What it means
RelationalOptionsExtension.WithCommandTimeout guards its input: a negative value is rejected (commandTimeout is < 0). null means 'unset' and is allowed, and 0 is permitted by the guard (the message wording 'positive number' is looser than the code). The exception is thrown eagerly when the option is set, not at query time.
Solutions
- Pass null to clear the timeout, or a non-negative int (the guard accepts 0).
- Validate user-supplied input (env var, config) before passing it: coerce negatives to null or a sensible default.
- If you meant 'unlimited'/default, pass null rather than a sentinel like -1.
Example fix
// before var timeout = int.Parse(config["CommandTimeout"]!); // could be -1 optionsBuilder.UseSqlServer(conn).CommandTimeout(timeout); // after var raw = config["CommandTimeout"]; var timeout = int.TryParse(raw, out var t) && t >= 0 ? t : (int?)null; optionsBuilder.UseSqlServer(conn).CommandTimeout(timeout);
Defensive patterns
Strategy: validation
Validate before calling
// Coerce user-supplied timeout to null-or-non-negative before setting the option.
static int? SafeCommandTimeout(string? raw)
=> int.TryParse(raw, out var v) && v >= 0 ? v : (int?)null; Type guard
static bool IsValidCommandTimeout(int? value) => value is null || value >= 0;
Prevention
- Validate config/env values (env vars, appsettings) before passing to CommandTimeout.
- Use null to express 'unset/default' rather than sentinels like -1.
- Centralize options construction so timeouts flow through one validated helper.
When it happens
Trigger: Calling optionsBuilder.UseXxx(...).CommandTimeout(-5), or RelationalOptionsExtension.WithCommandTimeout(-1) directly; computing the timeout from configuration and feeding a negative parsed value.
Common situations: Parsing CommandTimeout from an environment variable / appsettings that can be negative or unset (parsed as -1); sign errors in arithmetic; unit tests injecting bad values.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- The specified 'MaxBatchSize' value
- The specified 'MinBatchSize' value
- Multiple relational database provider configurations found…
- No relational database providers are configured. Configure…
- Relational-specific methods can only be used when the…
AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11).
Data as JSON: /api/errors/1a7458cf94f2ccf6.
Report an issue: GitHub.
Appendix: source
Thrown at src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs:169
}
/// <summary>
/// The command timeout, or <see langword="null" /> if none has been set.
/// </summary>
public virtual int? CommandTimeout
=> _commandTimeout;
/// <summary>
/// Creates a new instance with all options the same as for this instance, but with the given option changed.
/// It is unusual to call this method directly. Instead use <see cref="DbContextOptionsBuilder" />.
/// </summary>
/// <param name="commandTimeout">The option to change.</param>
/// <returns>A new instance with the option changed.</returns>
public virtual RelationalOptionsExtension WithCommandTimeout(int? commandTimeout)
{
if (commandTimeout is < 0)
{
throw new InvalidOperationException(RelationalStrings.InvalidCommandTimeout(commandTimeout));
}
var clone = Clone();
clone._commandTimeout = commandTimeout;
return clone;
}
/// <summary>
/// The maximum number of statements that will be included in commands sent to the database
/// during <see cref="DbContext.SaveChanges()" /> or <see langword="null" /> if none has been set.
/// </summary>
public virtual int? MaxBatchSize
=> _maxBatchSize;
/// <summary>
/// Creates a new instance with all options the same as for this instance, but with the given option changed.View on GitHub (pinned to 3a2006ef56)