microsoft/aspire · error · IllegalArgumentException
Cannot use null in a reference expression
Error message
Cannot use null in a reference expression
What it means
DistributedApplicationPipeline.AddStep enforces that pipeline step names are unique. Before creating the PipelineStep it scans _steps for the given name and throws an InvalidOperationException if a step with that name already exists, because steps are identified and ordered by name (dependency edges reference names).
Solutions
- Use a unique name per AddStep call — prefix or suffix with the resource name in loops (e.g. $"build-{resource.Name}").
- Check for an existing step before adding: skip registration if pipeline already contains the name, or update the existing step instead.
- If an integration adds the duplicate, gate it behind a flag or add it only once at the app level.
- Rename one of the colliding steps so dependency references (dependsOn/requiredBy) still point at the intended step.
Example fix
// before
pipeline.AddStep("build", context => ...);
pipeline.AddStep("build", context => ...); // throws
// after
pipeline.AddStep("build", context => ...);
pipeline.AddStep("test", context => ...); // unique names Defensive patterns
Strategy: validation
Validate before calling
// C#: assert uniqueness before calling AddStep
var names = new HashSet<string>(StringComparer.Ordinal);
void SafeAddStep(DistributedApplicationPipeline pipeline, string name, Func<PipelineStepContext, Task> action)
{
if (!names.Add(name)) throw new InvalidOperationException($"Duplicate pipeline step '{name}'");
pipeline.AddStep(name, action);
} Try / catch
try
{
pipeline.AddStep(name, action);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("has already been added to the pipeline"))
{
logger.LogWarning(ex, "Step '{StepName}' already registered; skipping.", name);
} Prevention
- Derive step names from resource names or GUID suffixes in loops.
- Centralize well-known step names in constants to avoid accidental collisions across integrations.
- Before adding a step, check whether your extension already registered it earlier in the builder chain.
When it happens
Trigger: Calling AddStep twice with the same name value on one pipeline instance; a custom pipeline extension adding a well-known step (e.g. 'build' or 'publish') that the app already registered; tests like ExecuteAsync_WithComplexDependencyGraph_ExecutesInCorrectOrder that exercise AddStep collisions.
Common situations: Registering the same custom step in both a global callback and per-resource callbacks; library integrations that unconditionally AddStep on every builder call while the user also adds a step of that name; loop-based step registration without name suffixes.
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
- Failed to send request
- getValue is only available on server-returned…
- HMP1 protocol error: invalid frame length
- Union value is of type
- A circular lifetime reference was detected for resource
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/f4318a833d8d5cd2.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting.CodeGeneration.Java/Resources/Base.java:255
reqArgs.put("context", AspireClient.serializeValue(handle));
if (cancellationToken != null) {
reqArgs.put("cancellationToken", cancellationToken);
}
return (String) client.invokeCapability("Aspire.Hosting.ApplicationModel/getValue", reqArgs);
}
public static ReferenceExpression refExpr(String format, Object... valueProviders) {
return new ReferenceExpression(format, valueProviders);
}
public static ReferenceExpression createConditional(Object condition, String matchValue, ReferenceExpression whenTrue, ReferenceExpression whenFalse) {
return new ReferenceExpression(condition, matchValue, whenTrue, whenFalse);
}
private static Object extractValueProvider(Object value) {
if (value == null) {
throw new IllegalArgumentException("Cannot use null in a reference expression");
}
if (value instanceof String || value instanceof Number || value instanceof Boolean) {
return value;
}
return AspireClient.serializeValue(value);
}
}
/**
* AspireList is a handle-backed list with lazy handle resolution.
*/
class AspireList<T> extends HandleWrapperBase {
private final String getterCapabilityId;
private Handle resolvedHandle;
AspireList(Handle handle, AspireClient client) {View on GitHub (pinned to 25830f84bd)