Skip to content

PA00001

Emily Taylor edited this page Jul 23, 2026 · 1 revision

PA00001: Methods that return Task should be async

Category Usage
Severity Warning

Cause

A method has a body (or expression body), returns Task/ValueTask, and isn't marked async. Interface and abstract signatures aren't flagged: there's no body to make async.

Rule description

A method shaped like an async method but not marked async usually means one of two things:

  • A: It's dead code waiting for an await that was never added
  • B: It's deliberately forwarding a task without awaiting it.

Without async/await, the compiler doesn't build a state machine for the method, so its frame doesn't participate in the async call chain. If an exception surfaces later in whatever the task represents, the stack trace jumps straight to wherever the task was eventually awaited.

Task Sync() => DoWorkAsync();               // PA00001
async Task Async() => await DoWorkAsync();  // fine

async Task DoWorkAsync()
{
    await Task.Delay(10);
    throw new InvalidOperationException("boom");
}

Calling Sync(), the exception's stack trace (simplified) reads:

System.InvalidOperationException: boom
   at DoWorkAsync()
   at Program.<Main>d__0.MoveNext()

Calling Async(), it reads:

System.InvalidOperationException: boom
   at DoWorkAsync()
   at Async()
   at Program.<Main>d__0.MoveNext()

Sync() clocked out before DoWorkAsync got to the interesting part, so it isn't around to answer for it. That method disappears from a debugger, from logs, from an error-reporting tool: from the whole story of how the failure happened. Root-causing production incidents gets harder for the sake of skipping one await.

How to fix

Add the async modifier and await the work being done:

// Before - PA00001: "Greet returns a Task and should be async"
public Task Greet()
{
    return Task.CompletedTask;
}
// After
public async Task Greet()
{
    await Task.CompletedTask;
}

When to suppress

If a method genuinely just forwards an already-completed or cached task with no await-able work of its own, suppress the specific instance rather than disabling the rule:

#pragma warning disable PA00001 // Intentionally synchronous - no work to await
public Task Greet() => Task.CompletedTask;
#pragma warning restore PA00001

A decorator chain is a common example. A pure passthrough delegating to inner can be noise in a stacktrace and is a good candidate for suppression:

public class LoggingRepository : IRepository
{
    private readonly IRepository _inner;

    public LoggingRepository(IRepository inner) => _inner = inner;

    #pragma warning disable PA00001 // Pure delegation - nothing in this frame to root-cause
    public Task<Order> GetOrderAsync(int id) => _inner.GetOrderAsync(id);
    #pragma warning restore PA00001
}

Clone this wiki locally