microsoft/autogen · error · InvalidOperationException

Method {methodInfo.Name} must return a ValueTask or ValueTas

Error message

Method {methodInfo.Name} must return a ValueTask or ValueTask<T>

What it means

Thrown by HandlerInvoker when the handler method's return type is neither ValueTask nor ValueTask<T>. The dispatcher type-erases handler results into ValueTask<object?> and requires the ValueTask-based shape; Task-returning or synchronous handlers are rejected at invoker construction time.

Source

Thrown at dotnet/src/Microsoft.AutoGen/Core/HandlerInvoker.cs:65

        {
            MethodInfo typeEraseAwait = typeof(HandlerInvoker)
                    .GetMethod(nameof(TypeEraseAwait), BindingFlags.NonPublic | BindingFlags.Static)!
                    .MakeGenericMethod(methodInfo.ReturnType.GetGenericArguments()[0]);

            getResultAsync = async
            (object? message, MessageContext messageContext) =>
            {
                object valueTask = invocation(message, messageContext)!;
                object? typelessValueTask = typeEraseAwait.Invoke(null, new object[] { valueTask });

                Debug.Assert(typelessValueTask is ValueTask<object?>);

                return await (ValueTask<object?>)typelessValueTask;
            };
        }
        else
        {
            throw new InvalidOperationException($"Method {methodInfo.Name} must return a ValueTask or ValueTask<T>");
        }

        this.Invocation = getResultAsync;
    }

    private Func<object?, MessageContext, ValueTask<object?>> Invocation { get; }

    public ValueTask<object?> InvokeAsync(object? obj, MessageContext messageContext)
    {
        return this.Invocation(obj, messageContext);
    }
}

View on GitHub (pinned to 027ecf0a37)

Solutions

  1. Declare handlers as `public ValueTask HandleAsync(T message, MessageContext ctx)` or `ValueTask<TResult>`
  2. Change Task-returning bodies to `return ValueTask.CompletedTask;` style instead of async Task
  3. After upgrading the framework, recheck handler signatures against the current IHandle<T> contract

Example fix

// before
public async Task HandleAsync(MyMessage msg, MessageContext ctx) { ... }

// after
public async ValueTask HandleAsync(MyMessage msg, MessageContext ctx) { ... }
Defensive patterns

Strategy: validation

Validate before calling

var rt = methodInfo.ReturnType;
if (!typeof(ValueTask).IsAssignableFrom(rt) && !(rt.IsGenericType && rt.GetGenericTypeDefinition() == typeof(ValueTask<>)))
    throw new InvalidOperationException($"{methodInfo.Name} must return ValueTask or ValueTask<T>");

Type guard

static bool IsValueTaskHandler(MethodInfo m)
{
    var t = m.ReturnType;
    return t == typeof(ValueTask) || (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueTask<>));
}

Try / catch

try { new HandlerInvoker(method, target); } catch (InvalidOperationException ex) when (ex.Message.Contains("must return a ValueTask")) { throw new InvalidOperationException($"Handler {method.Name} signature invalid; change return type to ValueTask", ex); }

Prevention

When it happens

Trigger: An IHandle<T>.HandleAsync implementation declared as `public async Task HandleAsync(...)` or `public void HandleAsync(...)` instead of ValueTask/ValueTask<T>; signature drift after upgrading the framework when handlers changed shape.

Common situations: Porting handlers written for a Task-based API; auto-fix or AI tooling rewriting ValueTask to Task; copying sample handlers from an older version.

Related errors


AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15). Data as JSON: /api/errors/a3c33e19f45606b8. Report an issue: GitHub.