microsoft/semantic-kernel · error · InvalidOperationException

Runtime is already running.

Error message

Runtime is already running.

What it means

InProcessRuntime.StartAsync throws InvalidOperationException if _shutdownSource is already non-null, which denotes the runtime is started. There is no restart path until FinishAsync (triggered by StopAsync/RunUntilIdleAsync) nulls _shutdownSource.

Source

Thrown at dotnet/src/Agents/Runtime/InProcess/InProcessRuntime.cs:56

    /// <inheritdoc/>
    public async ValueTask DisposeAsync()
    {
        await this.RunUntilIdleAsync().ConfigureAwait(false);
        this._shutdownSource?.Dispose();
        this._finishSource?.Dispose();
    }

    /// <summary>
    /// Starts the runtime service.
    /// </summary>
    /// <param name="cancellationToken">Token to monitor for shutdown requests.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    /// <exception cref="InvalidOperationException">Thrown if the runtime is already started.</exception>
    public Task StartAsync(CancellationToken cancellationToken = default)
    {
        if (this._shutdownSource != null)
        {
            throw new InvalidOperationException("Runtime is already running.");
        }

        this._shutdownSource = new CancellationTokenSource();
        this._messageDeliveryTask = Task.Run(() => this.RunAsync(this._shutdownSource.Token), cancellationToken);

        return Task.CompletedTask;
    }

    /// <summary>
    /// Stops the runtime service.
    /// </summary>
    /// <param name="cancellationToken">Token to propagate when stopping the runtime.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    /// <exception cref="InvalidOperationException">Thrown if the runtime is in the process of stopping.</exception>
    public Task StopAsync(CancellationToken cancellationToken = default)
    {
        if (this._shutdownSource != null)
        {

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Start the runtime once per lifecycle; call StopAsync/RunUntilIdleAsync or DisposeAsync before restarting.
  2. Create a new InProcessRuntime instance for each independent run.
  3. Guard startup with a flag so repeated StartAsync calls are no-ops.

Example fix

// before
runtime.StartAsync();
// ...
runtime.StartAsync(); // throws

// after
runtime.StartAsync();
// ...
await runtime.RunUntilIdleAsync();
runtime.StartAsync(); // ok after idle finishes
Defensive patterns

Strategy: validation

Validate before calling

bool started = false;
void SafeStart(InProcessRuntime rt)
{
    try { rt.StartAsync(); started = true; }
    catch (InvalidOperationException) { /* already started */ }
}

Try / catch

try { runtime.StartAsync(); }
catch (InvalidOperationException) { /* already running */ }

Prevention

When it happens

Trigger: Calling StartAsync twice without stopping/disposing the runtime; reusing one InProcessRuntime across sequential test scenarios; a DI singleton runtime started in multiple places.

Common situations: Test harnesses that start the runtime per test but share an instance; background services that start the runtime on each trigger; forgetting DisposeAsync resets state only via RunUntilIdleAsync (it does not null _shutdownSource unless the delivery task finishes).

Related errors


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