deepseek-ai/deepseek-harness · error
unknown session "${sessionId}"
Error message
unknown session "${sessionId}" What it means
Thrown by the ui-workspace browser plugin's renameSession callback in the dsh web client. Rename is a per-session verb on the ISession face, not a sessions-list verb, so the callback first resolves the id through ctx.sessions.binding(sessionId). When the binding registry holds no live binding for that id, rename cannot proceed and the id is echoed verbatim.
Source
Thrown at packages/client/ui-workspace/src/client/index.ts:84
// source identity): true while the surface's directory-flow hole is filled.
const flowSource = (hole: 'sidebar.workspaces.directoryFlow' | 'conversation.hero.workspace.directoryFlow'): HostObservable<boolean> => ({
getSnapshot: () => ctx.slots.entries(hole).length > 0,
subscribe: listener => ctx.slots.subscribe(hole, listener),
})
const browserFlowSource = flowSource('sidebar.workspaces.directoryFlow')
const pickerFlowSource = flowSource('conversation.hero.workspace.directoryFlow')
const browserInjected = (): WorkspaceBrowserInjected => ({
// Explicit group actions keep their target; unscoped New Session inherits
// the current Session Workspace before the recent-Workspace fallback.
startSession: (workspaceId) => { ctx.workspaces.startSession(workspaceId) },
open: (sessionId) => { ctx.sessions.open(sessionId) },
searchSessions,
searchResultLimit: ctx.sessions.searchResultLimit,
renameSession: async (sessionId, title) => {
// Row → session-face hop: rename is a per-session verb (ISession), not
// a list-service verb; the binding resolves any listed session.
const session = ctx.sessions.binding(sessionId)?.session
if (session === undefined) throw new Error(`unknown session "${sessionId}"`)
const result = await session.rename(title)
if (!result.ok) throw new Error(result.error.message)
},
forkSession: (sessionId) => {
ctx.sessions.fork({ sessionId, increaseTitle: true })
.then((childId) => { ctx.sessions.open(childId) })
.catch(() => {
// Fork or child-rename failure keeps the current selection.
})
},
renameWorkspace: async (workspaceId, title) => { await ctx.workspaces.rename(workspaceId, title) },
deleteWorkspace: async (workspaceId) => { await ctx.workspaces.delete(workspaceId) },
insertWorkspaceBefore: async (workspaceId, beforeWorkspaceId) => {
await ctx.workspaces.insertBefore(workspaceId, beforeWorkspaceId)
},
archiveSession: async (sessionId) => { await ctx.workspaces.archiveSession(sessionId) },
insertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {
await ctx.workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId)View on GitHub (pinned to b150a551b8)
Solutions
- Re-run the session list or search so the row and its binding are current, then retry the rename.
- Pass the session id taken from the live row at action time, not from stale component state or an old search result.
- If concurrent deletion is normal in your flow, catch this error, drop or deselect the row, and refresh — do not retry blindly.
- Verify you are not passing a workspace id; workspace renames go through ctx.workspaces.rename, not renameSession.
Example fix
// before — id captured when the row rendered; may be stale by commit time
await renameSession(row.id, title)
// after — re-resolve the binding at action time; refresh when it is gone
const session = sessions.binding(row.id)?.session
if (session === undefined) { await refreshSessions(); return }
await session.rename(title) Defensive patterns
Strategy: validation
Validate before calling
const binding = sessions.binding(sessionId)
if (binding?.session === undefined) {
await refreshSessions() // row is stale; re-sync before acting
return
}
await binding.session.rename(title) Type guard
function isBoundSession(sessions: SessionsService, id: string): boolean {
return sessions.binding(id)?.session !== undefined
} Try / catch
try {
await renameSession(sessionId, title)
} catch (err) {
if (err instanceof Error && err.message.startsWith('unknown session')) {
// stale row: drop/deselect it and refresh the list; do not retry blindly
await refreshSessions()
} else throw err
} Prevention
- Derive session ids from live rows at action time, never from cached search results.
- Refresh the session list after reconnect or window focus.
- Disable the rename affordance for rows whose binding is already gone.
When it happens
Trigger: Calling the injected renameSession(sessionId, title) (committing a rename in the workspace browser) with an id that has no binding: the session was closed or deleted on the host after its row was rendered, the id came from a stale search result, or the sessions list refreshed and dropped the row between render and commit.
Common situations: Session deleted from another tab or client while the rename dialog was open; reconnect dropped live bindings; passing a workspace id where a session id is expected; ids captured from an old searchResults array.
Related errors
- fork child rename failed: ${renamed.error.code}: ${renamed.e
- conversation.send failed: ${result.error.code}: ${result.err
- conversation.cancel failed: ${result.error.code}: ${result.e
AI-assisted analysis of deepseek-ai/deepseek-harness@b150a551b8 (2026-08-24).
Data as JSON: /api/errors/a535a9062dddcf66.
Report an issue: GitHub.