CherryHQ/cherry-studio · error · Error
Cannot stream on orphan session ${session.id} — its agent wa
Error message
Cannot stream on orphan session ${session.id} — its agent was deleted What it means
Thrown by ChannelMessageHandler.collectStreamResponse when the session being streamed has no agentId (falsy). Agent sessions can outlive their agent: when an agent is deleted, lingering channel sessions may keep rows with a null/cleared agentId. Streaming a completion requires a live agent, so an orphan session is a hard error rather than a silent no-op.
Source
Thrown at src/main/ai/channels/ChannelMessageHandler.ts:792
}
return agentSessionService.create({
agentId,
name: 'Channel session',
workspace: channelRow.workspace
})
}
private async collectStreamResponse(
session: AgentSessionEntity,
content: string,
abortController: AbortController,
adapter: ChannelAdapter,
chatId: string,
replyToMessageId?: string,
onAdmitted?: () => void
): Promise<string> {
if (!session.agentId) {
throw new Error(`Cannot stream on orphan session ${session.id} — its agent was deleted`)
}
let resolveExecution!: (text: string) => void
let rejectExecution!: (err: unknown) => void
const executionDone = new Promise<string>((resolve, reject) => {
resolveExecution = resolve
rejectExecution = reject
})
let accumulatedText = ''
const sentinel: StreamListener = {
id: `channel-completion:${chatId}`,
onChunk(chunk) {
// `text-delta`'s field is `delta`, not `text` (AI SDK `UIMessageChunk`).
if (chunk.type === 'text-delta') accumulatedText += chunk.delta
},
onDone() {
resolveExecution(accumulatedText.trim())
},View on GitHub (pinned to 726446b54c)
Solutions
- When deleting an agent, also close/delete its channel sessions and evict them from the sessionTracker.
- Before streaming, validate session.agentId and short-circuit (skip + log) instead of streaming.
- Add a cleanup pass that prunes sessions whose agentId no longer resolves to an agent.
- Ensure sessionTracker eviction runs on agent deletion, not only on session creation.
Example fix
// before: stream regardless of agent binding
await collectStreamResponse(session, ...)
// after: guard and clean up orphans
if (!session.agentId) {
logger.warn('Skipping orphan channel session', { sessionId: session.id })
await agentSessionService.delete(session.id)
return
}
await collectStreamResponse(session, ...) Defensive patterns
Strategy: type-guard
Validate before calling
if (!session.agentId) {
logger.warn('Orphan channel session; cleaning up', { sessionId: session.id })
await agentSessionService.delete(session.id).catch(() => {})
return // do not attempt to stream
} Type guard
function sessionHasAgent(s: { agentId?: string | null }): boolean {
return typeof s.agentId === 'string' && s.agentId.length > 0
} Try / catch
try {
await collectStreamResponse(session, content, controller, adapter, chatId, replyTo, onAdmitted)
} catch (e) {
if (e instanceof Error && /orphan session/.test(e.message)) {
await agentSessionService.delete(session.id).catch(() => {})
// surface 'agent was deleted' to the channel instead of crashing
} else throw e
} Prevention
- Delete/close channel sessions when their agent is deleted.
- Evict orphan sessions from the sessionTracker on agent deletion.
- Run a periodic cleanup of sessions whose agentId no longer resolves.
When it happens
Trigger: collectStreamResponse is invoked with a session whose agentId is null/undefined — the agent was deleted but the session row (and its channel binding) was not cleaned up.
Common situations: Agent deleted while channel sessions referencing it still exist; cascading delete that cleared agentId but left the session rows; orphaned session tracker entries pointing at agent-less sessions.
Related errors
- Channel not found: ${channelId}
- Agent not found: ${agentId}
- Agent session ${session.id} became invalid while starting ta
- Agent data file must be a real file: ${filePath}
- Agent data directory already exists: ${agentDataPath}
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/ee0df993c8fd3570.
Report an issue: GitHub.