microsoft/semantic-kernel · error · InvalidOperationException

Application is already stopped.

Error message

Application is already stopped.

What it means

AgentsApp.ShutdownAsync uses Interlocked.Exchange to flip _runningCount from 1 to 0; if it was not 1 (i.e. the app is not running) it throws InvalidOperationException. This prevents stopping a host that was never started or was already stopped.

Source

Thrown at dotnet/src/Agents/Runtime/Core/AgentsApp.cs:70

    public async ValueTask StartAsync()
    {
        if (Interlocked.Exchange(ref this._runningCount, 1) != 0)
        {
            throw new InvalidOperationException("Application is already running.");
        }

        await this.Host.StartAsync().ConfigureAwait(false);
    }

    /// <summary>
    /// Shuts down the application by stopping the host.
    /// Throws an exception if the application is not running.
    /// </summary>
    public async ValueTask ShutdownAsync()
    {
        if (Interlocked.Exchange(ref this._runningCount, 0) != 1)
        {
            throw new InvalidOperationException("Application is already stopped.");
        }

        await this.Host.StopAsync().ConfigureAwait(false);
    }

    /// <summary>
    /// Publishes a message to the specified topic.
    /// If the application is not running, it starts the host first.
    /// </summary>
    /// <typeparam name="TMessage">The type of the message being published.</typeparam>
    /// <param name="message">The message to publish.</param>
    /// <param name="topic">The topic to which the message will be published.</param>
    /// <param name="messageId">An optional unique identifier for the message.</param>
    /// <param name="cancellationToken">A token to cancel the operation if needed.</param>
    public async ValueTask PublishMessageAsync<TMessage>(TMessage message, TopicId topic, string? messageId = null, CancellationToken cancellationToken = default)
        where TMessage : notnull
    {
        if (Volatile.Read(ref this._runningCount) == 0)

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Only call ShutdownAsync if StartAsync (or an auto-starting publish) succeeded.
  2. Track started state yourself and skip shutdown when not running.
  3. Catch and swallow InvalidOperationException in best-effort shutdown paths if appropriate.

Example fix

// before
await app.StartAsync();
// ... work ...
await app.ShutdownAsync();
await app.ShutdownAsync(); // throws

// after
await app.StartAsync();
bool started = true;
try { /* work */ }
finally { if (started) await app.ShutdownAsync(); }
Defensive patterns

Strategy: validation

Validate before calling

bool started = false;
try { await app.StartAsync(); started = true; }
catch (InvalidOperationException) { /* was already running */ started = true; }
// later:
if (started) await app.ShutdownAsync();

Try / catch

try { await app.ShutdownAsync(); }
catch (InvalidOperationException) { /* not running; nothing to stop */ }

Prevention

When it happens

Trigger: Calling ShutdownAsync without a prior StartAsync; calling ShutdownAsync twice; calling ShutdownAsync after PublishMessageAsync never actually transitioned the app to running (e.g. it threw).

Common situations: Cleanup/dispose handlers that shut down unconditionally even when startup failed; double shutdown in finally blocks across concurrent paths; tests reusing an app instance.

Related errors


AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13). Data as JSON: /api/errors/59268e8c4d98a279. Report an issue: GitHub.