microsoft/semantic-kernel · error · InvalidOperationException

This thread has been deleted and cannot be used anymore.

Error message

This thread has been deleted and cannot be used anymore.

What it means

Thrown by AzureAIAgentThread.GetMessagesAsync when IsDeleted is true. This is the Azure AI-specific message retrieval method that enumerates all messages in a thread. After DeleteAsync has completed (IsDeleted = true), calling GetMessagesAsync is rejected because the underlying thread no longer exists on the service.

Source

Thrown at dotnet/src/Agents/AzureAI/AzureAIAgentThread.cs:156

            {
                throw new AgentThreadOperationException(ErrorMessage, ex);
            }
        }
    }

    /// <summary>
    /// Asynchronously retrieves all messages in the thread.
    /// </summary>
    /// <param name="sortOrder">The order to return messages in.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>The messages in the thread.</returns>
    /// <exception cref="InvalidOperationException">The thread has been deleted.</exception>
    [Experimental("SKEXP0110")]
    public async IAsyncEnumerable<ChatMessageContent> GetMessagesAsync(ListSortOrder? sortOrder = default, [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        if (this.IsDeleted)
        {
            throw new InvalidOperationException("This thread has been deleted and cannot be used anymore.");
        }

        if (this.Id is null)
        {
            await this.CreateAsync(cancellationToken).ConfigureAwait(false);
        }

        await foreach (var message in AgentThreadActions.GetMessagesAsync(this._client, this.Id!, sortOrder, cancellationToken).ConfigureAwait(false))
        {
            yield return message;
        }
    }
}

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Retrieve messages before calling DeleteAsync.
  2. Guard with: if (!thread.IsDeleted) { await foreach (var m in thread.GetMessagesAsync()) {...} }
  3. Cache messages locally before deletion if post-deletion access is needed.

Example fix

// before
await thread.DeleteAsync(ct);
await foreach (var msg in thread.GetMessagesAsync()) { ... } // throws

// after
await foreach (var msg in thread.GetMessagesAsync()) { history.Add(msg); }
await thread.DeleteAsync(ct);
Defensive patterns

Strategy: validation

Validate before calling

if (!azureThread.IsDeleted)
{
    await foreach (var msg in azureThread.GetMessagesAsync(cancellationToken: ct))
    {
        messages.Add(msg);
    }
}

Try / catch

try
{
    await foreach (var msg in thread.GetMessagesAsync(cancellationToken: ct))
    {
        history.Add(msg);
    }
}
catch (InvalidOperationException ex) when (ex.Message.Contains("has been deleted"))
{
    _logger.LogWarning("Cannot retrieve messages: thread was deleted.");
}

Prevention

When it happens

Trigger: Calling azureThread.GetMessagesAsync() after the thread has been deleted via DeleteAsync. The method checks IsDeleted before attempting lazy creation or message retrieval.

Common situations: Fetching message history for logging/display after the conversation cleanup has already run. Caching the thread reference and calling GetMessagesAsync in a post-session report. Race condition between a cleanup task and a history-retrieval task sharing the same thread reference.

Related errors


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