microsoft/aspire · error · InvalidOperationException
Resource command argument
Error message
Resource command argument '{name}' must be a string, number, boolean, or null. What it means
Individual resource command argument values must serialize to string, number, boolean, or null, because they are converted into InteractionInputCollection entries. Nested objects or arrays inside the arguments payload cannot be converted and throw this error.
Solutions
- Flatten nested structures into scalar arguments (string/number/boolean/null).
- Serialize complex values to a JSON string yourself if the command supports it, e.g. {"optionsJson": "{\"a\":1}"}.
- Check the command's declared input schema and send only the defined scalar inputs.
- Update the client tooling to reject nested values before sending.
Example fix
// before
new JsonObject { ["options"] = new JsonObject { ["a"] = 1 } }
// after
new JsonObject { ["optionsJson"] = "{\"a\":1}" } Defensive patterns
Strategy: validation
Validate before calling
bool AllScalar(JsonNode node) => node.GetValueKind() switch
{
JsonValueKind.Object => node.AsObject().All(p => AllScalar(p.Value!)),
JsonValueKind.Array => false,
_ => true
}; Type guard
static bool IsScalarArgument(JsonNode v) => v.GetValueKind() is JsonValueKind.String or JsonValueKind.Number or JsonValueKind.True or JsonValueKind.False or JsonValueKind.Null;
Try / catch
try { await rpc.ExecuteResourceCommandAsync(request); }
catch (InvalidOperationException ex) when (ex.Message.Contains("must be a string, number, boolean")) { logger.LogError(ex, "Flatten nested argument values before sending."); } Prevention
- Use only scalar values for each named argument.
- Serialize complex payloads into a JSON string argument if needed.
- Validate each argument value kind client-side before the RPC.
When it happens
Trigger: Calling ExecuteResourceCommandAsync with an arguments object/array containing a nested JSON object or array value for any argument name, e.g. {"options": {"a": 1}}.
Common situations: Clients assuming complex/nested payloads are supported; passing arrays of values for multi-select inputs; generic tools forwarding raw JSON config blobs as command arguments.
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
- Array params contains empty item
- At least one exporting assembly name is required.
- Deployment state must contain a JSON object.
- Endpoint option ' ' is configured more than once.
- Endpoint option names cannot be empty.
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/8bb99bd3fbafd265.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs:420
return arguments
.Select((value, index) => ConvertArgumentValue($"[{index}]", value))
.ToArray();
}
private static string? ConvertArgumentValue(string name, JsonNode? value)
{
if (value is null)
{
return null;
}
return value.GetValueKind() switch
{
JsonValueKind.String => value.GetValue<string>(),
JsonValueKind.Number => value.ToJsonString(),
JsonValueKind.True => "true",
JsonValueKind.False => "false",
_ => throw new InvalidOperationException($"Resource command argument '{name}' must be a string, number, boolean, or null.")
};
}
/// <summary>
/// Returns the discovery info needed to attach to a resource's terminal session(s). For a
/// resource configured with <c>WithTerminal()</c>, this enumerates the per-replica
/// consumer-side UDS endpoints by asking each per-replica terminal host process over its
/// control UDS in parallel. Returns <see cref="GetTerminalInfoResponse.IsAvailable"/> = false
/// when the resource has no <see cref="TerminalAnnotation"/>; per-host control RPC failures
/// (e.g. host not yet started) degrade to <see cref="TerminalReplicaInfo.IsAlive"/> = false
/// for that replica without failing the whole call.
/// </summary>
public async Task<GetTerminalInfoResponse> GetTerminalInfoAsync(GetTerminalInfoRequest request, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
var appModel = serviceProvider.GetRequiredService<DistributedApplicationModel>();
var resource = appModel.Resources.FirstOrDefault(r => string.Equals(r.Name, request.ResourceName, StringComparisons.ResourceName));View on GitHub (pinned to 25830f84bd)