microsoft/semantic-kernel · warning · ValueError
The thread has been deleted.
Error message
The thread has been deleted.
What it means
Raised by AzureAIAgentThread.get_messages when self._is_deleted is True. After a thread is deleted it cannot serve messages, so any further read is rejected. Thrown as a plain ValueError at the start of get_messages.
Source
Thrown at python/semantic_kernel/agents/azure_ai/azure_ai_agent.py:343
if (
not new_message.metadata
or "thread_id" not in new_message.metadata
or new_message.metadata["thread_id"] != self.id
):
assert self.id is not None # nosec
await AgentThreadActions.create_message(self._client, self.id, new_message)
async def get_messages(self, sort_order: Literal["asc", "desc"] = "desc") -> AsyncIterable[ChatMessageContent]:
"""Get the messages in the thread.
Args:
sort_order: The order to sort the messages in. Either "asc" or "desc".
Yields:
An AsyncIterable of ChatMessageContent of the messages in the thread.
"""
if self._is_deleted:
raise ValueError("The thread has been deleted.")
if self._id is None:
await self.create()
assert self.id is not None # nosec
async for message in AgentThreadActions.get_messages(self._client, self.id, sort_order=sort_order):
yield message
@experimental
@register_agent_type("foundry_agent")
class AzureAIAgent(DeclarativeSpecMixin, Agent):
"""Azure AI Agent class."""
client: AIProjectClient
definition: AzureAIAgentModel
polling_options: RunPollingOptions = Field(default_factory=RunPollingOptions)
mcp_tool_approval_callback: MCPToolApprovalCallback | None = None
channel_type: ClassVar[type[AgentChannel]] = AzureAIChannelView on GitHub (pinned to c028a0c7dc)
Solutions
- Do not call get_messages after delete; restructure teardown so reads happen before deletion.
- Track thread lifecycle in your code and skip reads when the thread is known to be deleted.
- Create a fresh AzureAIAgentThread for a new session instead of reusing a deleted one.
Example fix
// before await thread.delete() async for m in thread.get_messages(): ... // error // after async for m in thread.get_messages(): ... await thread.delete() // reads first, then delete
Defensive patterns
Strategy: validation
Validate before calling
async def safe_get_messages(thread):
if getattr(thread, '_is_deleted', False):
return []
return [m async for m in thread.get_messages()] Type guard
def thread_is_alive(thread) -> bool:
return not getattr(thread, '_is_deleted', False) Try / catch
try:
async for m in thread.get_messages(): ...
except ValueError as e:
if 'has been deleted' in str(e):
log.warning('skipping read on deleted thread')
else:
raise Prevention
- Order teardown so all reads complete before delete().
- Track thread liveness in your session manager and skip reads after delete.
- Create a new thread per session rather than reusing a deleted one.
When it happens
Trigger: Calling get_messages() after await thread.delete() completed; iterating history in a finally/teardown block that runs after deletion; holding a stale thread reference across a session reset.
Common situations: Cleanup ordering bug where history retrieval and deletion race; UI/test code that lists messages after the user/session teardown deleted the thread; reusing a thread object across multiple create/delete cycles without resetting the _is_deleted flag.
Related errors
- The thread cannot be deleted because it has not been created
- The thread could not be deleted due to an error response fro
- This thread has been deleted and cannot be used anymore.
- This thread has been deleted and cannot be recreated.
- This thread cannot be deleted, since it has not been created
AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13).
Data as JSON: /api/errors/b6bd8a0cf872a5ee.
Report an issue: GitHub.