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

  1. Re-run the session list or search so the row and its binding are current, then retry the rename.
  2. Pass the session id taken from the live row at action time, not from stale component state or an old search result.
  3. If concurrent deletion is normal in your flow, catch this error, drop or deselect the row, and refresh — do not retry blindly.
  4. 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

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


AI-assisted analysis of deepseek-ai/deepseek-harness@b150a551b8 (2026-08-24). Data as JSON: /api/errors/a535a9062dddcf66. Report an issue: GitHub.