elsa-workflows/elsa-core · error · InvalidOperationException
The method must return Task or ValueTask
Error message
The {methodInfo.Name} method must return Task or ValueTask What it means
After locating the middleware's Invoke/InvokeAsync method, GetInvokeMethod validates that it returns Task or ValueTask, because the pipeline awaits middleware via reflection. This error is thrown when the middleware entry-point method returns something else (void, sync result, custom type), which would break the awaitable pipeline contract.
Solutions
- Change the middleware method's return type to Task (use async/await and return Task) or ValueTask.
- If no async work exists, return Task.CompletedTask from a Task-returning method instead of void.
- Wrap existing sync logic: `public async ValueTask InvokeAsync(Ctx ctx, Del next) { DoWork(); await next(ctx); }`.
- Ensure the method name is exactly Invoke or InvokeAsync and it is a public instance method.
Example fix
// before
public void InvokeAsync(CommandContext context, CommandMiddlewareDelegate next)
{
next(context);
}
// after
public async ValueTask InvokeAsync(CommandContext context, CommandMiddlewareDelegate next)
{
await next(context);
} Defensive patterns
Strategy: validation
Validate before calling
// Validate the middleware entry point returns Task or ValueTask before registering:
var invoke = middlewareType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.FirstOrDefault(m => m.Name is "Invoke" or "InvokeAsync");
if (invoke != null &&
!typeof(Task).IsAssignableFrom(invoke.ReturnType) &&
!typeof(ValueTask).IsAssignableFrom(invoke.ReturnType))
throw new InvalidOperationException($"{middlewareType.Name}.{invoke.Name} must return Task or ValueTask."); Type guard
static bool IsAwaitableMiddleware(Type t)
{
var m = t.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.FirstOrDefault(x => x.Name is "Invoke" or "InvokeAsync");
return m != null && (typeof(Task).IsAssignableFrom(m.ReturnType) || typeof(ValueTask).IsAssignableFrom(m.ReturnType));
} Try / catch
try
{
pipeline.AddMiddleware(typeof(MyMiddleware));
}
catch (InvalidOperationException ex) when (ex.Message.Contains("must return Task or ValueTask"))
{
logger.LogError(ex, "{Type} middleware entry point is not awaitable", typeof(MyMiddleware).Name);
throw;
} Prevention
- Always declare middleware entry points as `async ValueTask` or returning Task/ValueTask.
- Never write void middleware; return Task.CompletedTask when no async work exists.
- Rely on the interface (IMiddleware) so the return type is compiler-checked.
- Avoid custom awaitable return types; the check accepts only Task/ValueTask.
When it happens
Trigger: Writing `public void InvokeAsync(...)` or `public string Invoke(...)`; returning a plain value or a non-awaitable custom wrapper type; using an older sync middleware signature in the async pipeline; a method named InvokeAsync that returns a custom builder type instead of Task/ValueTask.
Common situations: Porting synchronous middleware from another mediator library; writing middleware with side effects only and instinctively returning void; auto-generating middleware stubs with wrong return types; generic wrappers returning Task<T>-derived custom types that no longer satisfy the direct Task/ValueTask check.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Multiple Invoke methods were found. Use either Invoke or…
- No Invoke methods were found. Use either Invoke or…
- The method must return Task or ValueTask
- Multiple Invoke methods were found. Use either Invoke or…
- No Invoke methods were found. Use either Invoke or…
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/7292f3b99757894a.
Report an issue: GitHub.
Appendix: source
Thrown at src/common/Elsa.Mediator/Middleware/MiddlewareHelpers.cs:34
public static MethodInfo GetInvokeMethod(Type middleware)
{
const string invokeMethodName = "Invoke";
const string invokeAsyncMethodName = "InvokeAsync";
var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
var invokeMethods = methods.Where(m => string.Equals(m.Name, invokeMethodName, StringComparison.Ordinal) || string.Equals(m.Name, invokeAsyncMethodName, StringComparison.Ordinal)).ToArray();
switch (invokeMethods.Length)
{
case > 1:
throw new InvalidOperationException("Multiple Invoke methods were found. Use either Invoke or InvokeAsync.");
case 0:
throw new InvalidOperationException("No Invoke methods were found. Use either Invoke or InvokeAsync");
}
var methodInfo = invokeMethods[0];
if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType) && !typeof(ValueTask).IsAssignableFrom(methodInfo.ReturnType))
throw new InvalidOperationException($"The {methodInfo.Name} method must return Task or ValueTask");
return methodInfo;
}
}View on GitHub (pinned to fe9217bdfa)