Files
deepseek-harness/.agents/notes/implemented/bug-fix/2026-08-05-workspace-blank-session-reuse-membership.md
T
_Kerman 63ba836bce fix(client): address CI and review findings on blank-session reuse
- docs: fix cross-class note links (../feature/...), mirror the membership
  reuse criterion in README.zh.md and the connectWorkspace architecture
  note (EN/ZH/sidecars re-recorded)
- test(client): stray + member blank at the same path — the scan skips the
  stray and reuses the member
- test(web): re-registration after delete now mints a fresh blank session
  (the old cwd-only reuse is gone), so the account is no longer empty;
  assert the retained seeded session is still not re-adopted
2026-08-05 12:21:09 +08:00

3.8 KiB

Agent Note: Workspace New Session reuse hijacked cwd-matching unaccounted blank sessions

Status: implemented

English | 中文

Problem

Clicking the + on a Workspace group in the sidebar sometimes opened a session that the sidebar showed under Ungrouped instead of under the clicked Workspace — "entered a new session but the Workspace was not selected". The failure was specific to Workspaces registered at the directory the CLI runs from (in practice the harness checkout itself, i.e. defaults.cwd = process.cwd()), and appeared once a CLI-born blank session existed there.

Root cause: connectWorkspace's blank-session reuse scanned the session list mirror on cwd equality alone. The host's own membership rule requires both an id in the Workspace account (sessionIds) and a session header whose canonical cwd equals the Workspace path (Workspace UI product flow); a cwd match without the account slot is exactly the Ungrouped case. The reuse scan ignored the account slot, so any live blank session whose cwd matched qualified — including main-session-* sessions the CLI/TUI/headless entry points birth at the host cwd (session.create({}) falls back to defaults.cwd and never attaches to a Workspace). When such a session was live and blank (no turn/start yet), the next + click on a Workspace registered at that path reused it and navigation opened a session no grouping surface can show under that Workspace. Workspaces at other paths were unaffected because no unaccounted blank sessions accumulate there; the host-cwd Workspace accumulated one per CLI run.

Decision

The reuse scan now requires workspace membership: blank AND summary.cwd === workspace.path AND workspace.sessionIds.includes(summary.id) AND not archived. A cwd-only match falls through to session.create({ workspaceId }), which attaches the fresh session so the Workspace owns it — the same arm the flow already used for "no blank session exists".

Alternatives considered

Adopt the stray instead of minting. session.create({ workspaceId }) could attach a cwd-matching unaccounted blank session. Rejected: silently attaching CLI-born sessions to a Workspace crosses the account boundary by surprise, and the client cannot distinguish "stray" from "the Workspace's own blank" without the membership view — which is the fix itself.

Attach on reuse via a new wire operation. Requires a workspace.attachSession RPC in the navigation hot path and would still render the session under Ungrouped for a frame; no product need justifies the surface.

Consequences

Stray blank sessions remain visible in Ungrouped (the user can still open them) but are never hijacked by a Workspace's New Session flow. Membership is a new condition on the reuse scan, and it has one observable stale-mirror edge: in the window where the session mirror is fresh but the Workspace account frame lags, the Workspace's own member blank can fail the membership check and a duplicate blank is minted where the old code reused — a second New Session row under that Workspace rather than the old failure shape (a session that no grouping surface shows). Both windows are transient and the per-Workspace coalescing still prevents duplicate creates racing one another. No host, wire, or durable-format change.

Testing

packages/client/runtime/tests/workspaces-service.spec.ts covers the four outcomes: a member blank session is reused (no create RPC); a stray blank with matching cwd is not reused and a fresh accounted session is created (regression case); an archived blank is not reused; a rejected first prompt keeps a member blank eligible. The full client suite (pnpm run test:gui) stays green.