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 GetMessagesAsync when the thread's IsDeleted flag is true. IsDeleted is set to true by the base AgentThread.DeleteAsync, and once set the thread is permanently unusable. This is a lifecycle guard, not a service error.
Source
Thrown at dotnet/src/Agents/OpenAI/OpenAIAssistantAgentThread.cs:187
{
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(MessageCollectionOrder? 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 AssistantThreadActions.GetMessagesAsync(this._client, this.Id!, sortOrder, cancellationToken).ConfigureAwait(false))
{
yield return message;
}
}
}
View on GitHub (pinned to c028a0c7dc)
Solutions
- Do not call GetMessagesAsync after DeleteAsync — read messages first, then delete.
- Guard with 'if (!thread.IsDeleted)' before enumerating.
- Create a fresh OpenAIAssistantAgentThread for a new conversation instead of reusing a deleted one.
Example fix
// before await thread.DeleteAsync(); var msgs = await thread.GetMessagesAsync().ToListAsync(); // throws // after var msgs = await thread.GetMessagesAsync().ToListAsync(); await thread.DeleteAsync();
Defensive patterns
Strategy: validation
Validate before calling
if (!thread.IsDeleted)
{
var messages = await thread.GetMessagesAsync().ToListAsync();
} Prevention
- Read messages before calling DeleteAsync.
- Treat IsDeleted as terminal: never reuse a deleted thread.
- Centralize thread lifecycle so background tasks cannot outlive deletion.
When it happens
Trigger: await thread.DeleteAsync(); ... await thread.GetMessagesAsync(...); — any read of messages after the thread has been deleted. Also reachable if a prior DeleteAsync succeeded but the caller did not refresh its local reference and kept the same instance.
Common situations: Application flow deletes a conversation thread on session end but a background task (e.g. a logging/middleware pipeline) still enumerates messages; reuse of a thread object across requests after one request deleted it.
Related errors
- This thread has been deleted and cannot be recreated.
- This thread cannot be deleted, since it has not been created
- This thread has been deleted and cannot be used anymore.
- Thread has been deleted; call `create()` to recreate it.
- Cannot create thread because it has already been deleted.
AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13).
Data as JSON: /api/errors/5459f8d7bedb8fe3.
Report an issue: GitHub.