microsoft/aspire · error · RpcException

InvalidArgument

InvalidArgument

Error message

Resource command argument '{name}' must be a string, number, boolean, or null.

What it means

ConvertArgumentValue flattens a protobuf Value for a resource command argument into a string. Only string, number, bool, and null kinds are accepted; struct, list, or unset kinds cause an RpcException with StatusCode.InvalidArgument. The dashboard/client sent a command argument with a JSON type the command model cannot represent.

Solutions

  1. Send only scalar arguments: strings, numbers, booleans, or null for each command parameter.
  2. Flatten nested objects/arrays into separate scalar arguments or encode them as a JSON string before invoking the command.
  3. Log the full command payload and verify each argument's protobuf kind before the call.
  4. Update dashboard/client and AppHost to matching versions if the payload is produced by shipped tooling.

Example fix

// before
var value = new Value { StructValue = new Struct { Fields = { ["items"] = new Value { ListValue = new ListValue { Values = { new Value { StringValue = "a" } } } } } } };
// after
var value = new Value { StringValue = "a" }; // one scalar per argument; serialize lists to a JSON string if needed
Defensive patterns

Strategy: validation

Validate before calling

bool IsValidArgument(Value v) => v is { KindCase: Value.KindOneofCase.StringValue or Value.KindOneofCase.NumberValue or Value.KindOneofCase.BoolValue or Value.KindOneofCase.NullValue };

Type guard

bool IsScalarValue(Value v) => v.KindCase is Value.KindOneofCase.StringValue or Value.KindOneofCase.NumberValue or Value.KindOneofCase.BoolValue or Value.KindOneofCase.NullValue;

Try / catch

try { await client.ExecuteCommandAsync(cmd); } catch (RpcException ex) when (ex.StatusCode == StatusCode.InvalidArgument) { logger.LogError("Command argument rejected: {Detail}", ex.Status.Detail); }

Prevention

When it happens

Trigger: Calling the ExecuteResourceCommand gRPC with a CommandValues entry whose Value is a StructValue or ListValue (nested object/array), or leaving the oneof unset.

Common situations: A custom dashboard or automation client passes an array or object as a command argument; a UI bug serializes arguments as JSON structures instead of scalars.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at src/Aspire.Hosting/Dashboard/DashboardService.cs:473

        var values = new Dictionary<string, string?>(StringComparers.InteractionInputName);
        foreach (var field in arguments)
        {
            values[field.Key] = ConvertArgumentValue(field.Key, field.Value);
        }

        return values;
    }

    private static string? ConvertArgumentValue(string name, Value value)
    {
        return value.KindCase switch
        {
            Value.KindOneofCase.StringValue => value.StringValue,
            Value.KindOneofCase.NumberValue => value.NumberValue.ToString("R", CultureInfo.InvariantCulture),
            Value.KindOneofCase.BoolValue => value.BoolValue ? "true" : "false",
            Value.KindOneofCase.NullValue => null,
            _ => throw new RpcException(new Status(StatusCode.InvalidArgument, $"Resource command argument '{name}' must be a string, number, boolean, or null."))
        };
    }

    private async Task ExecuteAsync(Func<CancellationToken, Task> execute, ServerCallContext serverCallContext)
    {
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping, serverCallContext.CancellationToken);

        try
        {
            await execute(cts.Token).ConfigureAwait(false);
        }
        catch (OperationCanceledException) when (cts.Token.IsCancellationRequested)
        {
            // Ignore cancellation and just return.
        }
        catch (IOException) when (cts.Token.IsCancellationRequested)
        {
            // Ignore cancellation and just return. Cancelled writes throw IOException.

View on GitHub (pinned to 25830f84bd)