Fixed: "error CS8031: Async lambda expression converted to a 'Task' returning delegate cannot return a value"
Wrapping an endpoint's work in strategy.ExecuteAsync gave error CS8031: Async lambda expression converted to a 'Task' returning delegate cannot return a value. The code looks right, and it returns values on purpose. The compiler just can't work out one type for those values, and a small addition tells it.
What I Was Doing
I was moving a transaction into EF Core's execution strategy, in a .NET 10 minimal API. The endpoint returns either 201 Created or 404 Not Found:
var strategy = db.Database.CreateExecutionStrategy();
return await strategy.ExecuteAsync(
async ct =>
{
// ...
if (changed == 0)
{
return TypedResults.NotFound();
}
// ...
return TypedResults.Created(/* ... */);
},
cancellationToken);
dotnet build
What I Expected
Build succeeded.
What I Got
Build FAILED.
src/Dewiride.Inventory.Api/Features/Stock/StockEndpoints.cs(51,21): error CS8031: Async lambda expression converted to a 'Task' returning delegate cannot return a value
src/Dewiride.Inventory.Api/Features/Stock/StockEndpoints.cs(75,17): error CS8031: Async lambda expression converted to a 'Task' returning delegate cannot return a value

What I Checked
The lines it names. Both are return statements, one for each result type: NotFound and Created<T>.
What ExecuteAsync offers. The extension methods on IExecutionStrategy include one for a Func<CancellationToken, Task>, which returns no value, and one for Func<CancellationToken, Task<TResult>>. The message mentions a plain Task, so the compiler had picked the first.
Why It Happens
To use the Task<TResult> overload, the compiler has to infer TResult from your return statements. NotFound and Created<T> have no common type it can pick. They both convert to Results<Created<T>, NotFound>, but nothing here mentions that type. With inference failed, only the overload with no result is left, and a lambda for that one can't return a value.
The Fix
State the lambda's return type:
return await strategy.ExecuteAsync(
async Task<Results<Created<StockChangeResponse>, NotFound>> (CancellationToken ct) =>
{
// ...
},
cancellationToken);
Each return now converts to that type, as it does in a normal method. The parameter's type is optional here: (ct) built just as well, and I kept (CancellationToken ct) because it reads better.
After the Fix
Build succeeded.
0 Warning(s)
0 Error(s)

Common Mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
Casting each return to IResult | It compiles, and OpenAPI loses the result types | State the union type |
| Moving everything outside the lambda | No retry for it | Keep the whole transaction inside |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
A conversion error on one return | That type isn't in the Results<...> list | Add it |
| The lambda is hard to read | The union type is long | Move the body to a private method, and call it |
FAQ
Could I use a method instead of a lambda?
Yes. A private method with a normal return type works, and it's easier to read: strategy.ExecuteAsync(ct => ReceiveOnceAsync(productId, request, db, clock, ct), cancellationToken).
Conclusion
CS8031 here means the compiler couldn't infer one return type from your return statements. Write the type on the lambda, as in async Task<Results<...>> (CancellationToken ct) => ....
Additional Resources
- Lambda expressions — explicit return types.
- Connection Resiliency —
ExecuteAsync.
