Repository navigation
[Bug]: Concurrent dsh instances sharing one DSH_HOME wipe each other’s workspace session membership #1485
Replies: 3 comments
|
独立复现确认 + 读侧根因补充(macOS,0.1.1-rc.2,第三方 Desktop 壳 + 官方 web 组合) 独立遇到同类故障,并在 0.1.1-rc.2 产物源码中定位到根因。对本报告补充两点: 1. 常见真实组合零配置即踩中:第三方 DSH Desktop 壳 + 官方 web。 先澄清:我们的桌面端是第三方 DSH Desktop 壳(内嵌 server :43120),并非 deepseek-ai 官方应用;但它默认同样使用 2. 「列表陈旧」独立于「写侧丢更新」——读路径从不重扫。 因此即使修好写侧覆盖(merge-on-write 等),跨实例会话在没有重扫/通知机制时依然永远不会出现。反过来,只需在 3. 同一轮排查的关联发现
如需我们事故中的会话/ |
|
I turned this report into a source-backed English recovery guide: https://github.com/sandbaseai/deepseek-harness-handbook/blob/main/docs/en/troubleshooting/concurrent-dsh-home-session-membership.md — it documents the shared workspace-store race, safe DSH_HOME topologies, recovery steps, and tests that inspect durable membership. Feedback welcome. |
|
Additional data point: official Desktop app + official Both instances here are official builds with zero profile patching, so the usual "isolate the session root per profile" mitigation doesn't apply. Environment — macOS 26.7, arm64. Desktop: DeepSeek Harness.app 0.2.0-rc.2, profile Observed (2026-10-03 PDT)
Root cause still present at
Recovery — stop both writers, back up Ask — per |
Uh oh!
There was an error while loading. Please reload this page.
Bug report: concurrent dsh instances sharing one DSH_HOME wipe each other's workspace session membership
Summary
Running two
dsh webinstances (different profiles, differentsession-persistence-jsonlroots, same DSH_HOME)against the same workspace directory makes the sidebar's workspace group lose sessions:
every
session.createon one instance permanently removes the other instance's sessions from theworkspace account, so they fall into the sidebar's Ungrouped bucket. The user sees
"sessions keep moving from the workspace into Ungrouped" and the damage is durable
(the one-time bootstrap never re-adopts them).
Environment
C:\Users\<user>\.dshweb:dsh --profile web web→ 127.0.0.1:3080, session root$DSH_HOME/sessionsdesktop:dsh --profile desktop web→ 127.0.0.1:5478, session root$DSH_HOME/sessions-desktop(isolated via a profile patch on
session-persistence-jsonl.root, as recommended)$DSH_HOME/storages/workspace.json(
storage-json.root = dshHomePath('storages')inpackages/bundle/web-app/cordis.patch.ymlis home-level, not profile-scoped)Observed behavior (timeline, UTC)
15:30:16Z— web instance attaches sessionA(session-27caaf14…).workspace.jsonnow recordssessionIds: ["A"].15:37:01Z— desktop instance creates and attaches sessionB(session-e6d05dba…).workspace.jsonis rewritten withsessionIds: ["B"],updatedAt: 15:37:01Z.Ais gone from the account.workspace.list→sessionIds: ["A"]workspace.list→sessionIds: ["B"]["B"]Aappears under Ungrouped in the web sidebar.This happened repeatedly during the day: every attach on either instance left exactly the newest session
of whichever instance wrote last; all other sessions (including archived ones, whose slots should be kept)
ended up unaccounted.
Root cause
WorkspaceEntity.mutate()(packages/workspace/workspace/src/entity.ts) re-filtersrecord.sessionIdson every write:changed.sessionIds.filter(id => this.host.sessionPath(id) === changed.path).host.sessionPath(id)answers from the registry's in-process header index(
packages/workspace/workspace/src/index.ts), built at startup from this process's ownsessionPersistence.list(). A session stored under the other profile's session root issimply not in the index →
sessionPath(id) === undefined→ the id is durably pruned.storages/workspace.json) is home-level and shared by everyprofile, so the two registries fight over one file: last writer wins, and each writer
destroys the other's membership.
initialized === false), so prunedsessions never return to the workspace group.
The sessions' headers are all valid: every header's
cwdcanonicalizes exactly to the workspacepath (
E:\DshProject\Test), verified by decoding the zstd logs. So this is not a cwd/path mismatch.Suggested fix
The prune should be non-destructive for sessions the writer has not indexed:
WorkspaceEntity.mutate(), only drop an id when its canonical cwd is known and differsfrom the record path (or is known-invalid, e.g. cwd unresolvable / not a directory).
Keep ids that are merely absent from the writer's header index
(
sessionPath(id) === undefinedand the header isn't cached at all).The registry already distinguishes these two cases in
reportFilteredCandidates()(
session header is missingvscanonical cwd … differs), so the needed knowledge exists;it just needs to be exposed through the entity host (or the filter moved into the registry).
Alternative/complementary: document (or enforce) that two live instances must not share the
storage-jsonroot, mirroring the existing guidance forsession-persistence-jsonlroots.Repro sketch
Win instance 1.session.create {sessionId: A, workspaceId}on instance 1 (rootsessions-1).session.create {sessionId: B, workspaceId}on instance 2 (rootsessions-2, same DSH_HOME).workspace.liston both instances and inspect$DSH_HOME/storages/workspace.json:the account holds only the newest session of the last writer; the other was pruned.
Impact
Data is not lost (session logs are intact) but workspace organization silently degrades:
sessions the user worked on for hours land in "Ungrouped" and cannot get back into the
workspace group without manual re-attach via
session.createre-adoption.All reactions