After every release is installed and the container is recreated, older chats sit under Ungrouped instead of their project.
Cause (verified against this host's own state volume, dsh 0.1.5-rc.2)
dsh joins a session to a project when the session is created, against the project directory's realpath-canonicalized path. There is no later re-scan: upstream's history bootstrap runs once per state volume (initialized: true here since 2026-09-02) and never again. Re-adding a folder creates a fresh, empty project; existing chats are never moved into it.
So a project directory that the container update erases keeps its chats ungrouped for good. On this host, only /workspace and /home/node/.dsh are persisted, while the workspace picker opens in /home/node:
Project
Directory
Chats
/workspace/Validation
ok (persisted)
35 accounted, all grouped
/home/node/App Development
MISSING (container filesystem)
1 accounted + 3 recoverable
/home/node/Game Servers
MISSING
1 accounted
/home/node/Health
MISSING
1 accounted
/home/node/Sondra
ok, but ephemeral
1 accounted + 5 recoverable
The 8 recoverable chats are exactly the 8 rows the sidebar shows under Ungrouped.
Fix
dsh-workspace-doctor.mjs runs from the entrypoint before dsh starts:
reports every project directory that no longer resolves, with its storage, and every chat recoverable from one;
with DSH_WORKSPACE_DOCTOR=repair (the template default), recreates the missing directories and re-accounts the recoverable sessions, so the chats return to their project at the next start.
Safety: session logs are never modified, no project is invented, no chat is moved into a project it did not run in, the registry is backed up to workspace.json.bak-<stamp> first, the write is the same atomic 0600 fsynced replacement dsh's JSON backend performs, an unknown registry version is refused (exit 2), and the run is idempotent. Image default is report-only (warn); off is silent.
Verification
--check against the live state volume: reports 3 missing directories and the 8 recoverable chats, exit 1, writes nothing (registry md5 unchanged).
--all against a copy of it: exit 0, Validation 35 → 35, App Development 1 → 4, Sondra 1 → 6, one backup, second run a no-op.
The rewritten registry parses with dsh's own workspaceDomainState / workspaceRecord zod schemas and satisfies the startup invariants (registry order ↔ table, one owner per session).
Unknown-format and fresh-volume paths tested; entrypoint warn / repair / off branches tested with sh.
Note
This repairs grouping, not files: a recreated directory is empty, because the files lived in the container. Keeping project data under /workspace prevents the situation entirely, which the README now states as an image-contract rule. The same PR adds the doctor to the CI recipe hash and smoke test so the next built image carries it.
Authored by the dsh agent on the maintainer's behalf; review as you would any agent change.
## Symptom
After every release is installed and the container is recreated, older chats sit under **Ungrouped** instead of their project.
## Cause (verified against this host's own state volume, dsh 0.1.5-rc.2)
dsh joins a session to a project **when the session is created**, against the project directory's `realpath`-canonicalized path. There is no later re-scan: upstream's history bootstrap runs once per state volume (`initialized: true` here since 2026-09-02) and never again. Re-adding a folder creates a fresh, empty project; existing chats are never moved into it.
So a project directory that the container update erases keeps its chats ungrouped for good. On this host, only `/workspace` and `/home/node/.dsh` are persisted, while the workspace picker opens in `/home/node`:
| Project | Directory | Chats |
|---|---|---|
| `/workspace/Validation` | ok (persisted) | 35 accounted, all grouped |
| `/home/node/App Development` | MISSING (container filesystem) | 1 accounted + 3 recoverable |
| `/home/node/Game Servers` | MISSING | 1 accounted |
| `/home/node/Health` | MISSING | 1 accounted |
| `/home/node/Sondra` | ok, but ephemeral | 1 accounted + 5 recoverable |
The 8 recoverable chats are exactly the 8 rows the sidebar shows under Ungrouped.
## Fix
`dsh-workspace-doctor.mjs` runs from the entrypoint before dsh starts:
- reports every project directory that no longer resolves, with its storage, and every chat recoverable from one;
- with `DSH_WORKSPACE_DOCTOR=repair` (the template default), recreates the missing directories and re-accounts the recoverable sessions, so the chats return to their project at the next start.
Safety: session logs are never modified, no project is invented, no chat is moved into a project it did not run in, the registry is backed up to `workspace.json.bak-<stamp>` first, the write is the same atomic 0600 fsynced replacement dsh's JSON backend performs, an unknown registry version is refused (exit 2), and the run is idempotent. Image default is report-only (`warn`); `off` is silent.
## Verification
- `--check` against the live state volume: reports 3 missing directories and the 8 recoverable chats, exit 1, writes nothing (registry md5 unchanged).
- `--all` against a copy of it: exit 0, Validation 35 → 35, App Development 1 → 4, Sondra 1 → 6, one backup, second run a no-op.
- The rewritten registry parses with dsh's own `workspaceDomainState` / `workspaceRecord` zod schemas and satisfies the startup invariants (registry order ↔ table, one owner per session).
- Unknown-format and fresh-volume paths tested; entrypoint `warn` / `repair` / `off` branches tested with `sh`.
## Note
This repairs *grouping*, not files: a recreated directory is empty, because the files lived in the container. Keeping project data under `/workspace` prevents the situation entirely, which the README now states as an image-contract rule. The same PR adds the doctor to the CI recipe hash and smoke test so the next built image carries it.
Authored by the dsh agent on the maintainer's behalf; review as you would any agent change.
Updating the container recreates it, so anything outside the mounted
/workspace and state volume is gone. dsh joins a session to its project
when the session is created (upstream's history bootstrap runs once per
state volume and never again), and the workspace registry stores an
absolute canonical directory path — so a project directory under
/home/node, where the workspace picker opens by default, loses its chats
to "Ungrouped" permanently on the next update.
Ship dsh-workspace-doctor.mjs and run it from the entrypoint before dsh
starts: it reports every project directory that no longer resolves and
every chat recoverable from one, and with DSH_WORKSPACE_DOCTOR=repair it
recreates the missing directories and re-accounts those sessions in the
workspace registry. Session logs are never read for writing and never
modified, no project is invented, no chat is moved into a project it did
not run in, and the registry is copied to workspace.json.bak-<stamp>
before the atomic, 0600, fsynced write dsh's own JSON backend performs.
The doctor refuses to write a registry version it does not know, is
idempotent, and defaults to report-only in the image (warn) so nothing
changes unasked; the Unraid template defaults to repair.
CI: the doctor joins the recipe hash and the push path list, and the
image smoke test asserts it is installed and runs against a fresh volume
(exit 2, the tool itself failing, is the only outcome that fails a build).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Symptom
After every release is installed and the container is recreated, older chats sit under Ungrouped instead of their project.
Cause (verified against this host's own state volume, dsh 0.1.5-rc.2)
dsh joins a session to a project when the session is created, against the project directory's
realpath-canonicalized path. There is no later re-scan: upstream's history bootstrap runs once per state volume (initialized: truehere since 2026-09-02) and never again. Re-adding a folder creates a fresh, empty project; existing chats are never moved into it.So a project directory that the container update erases keeps its chats ungrouped for good. On this host, only
/workspaceand/home/node/.dshare persisted, while the workspace picker opens in/home/node:/workspace/Validation/home/node/App Development/home/node/Game Servers/home/node/Health/home/node/SondraThe 8 recoverable chats are exactly the 8 rows the sidebar shows under Ungrouped.
Fix
dsh-workspace-doctor.mjsruns from the entrypoint before dsh starts:DSH_WORKSPACE_DOCTOR=repair(the template default), recreates the missing directories and re-accounts the recoverable sessions, so the chats return to their project at the next start.Safety: session logs are never modified, no project is invented, no chat is moved into a project it did not run in, the registry is backed up to
workspace.json.bak-<stamp>first, the write is the same atomic 0600 fsynced replacement dsh's JSON backend performs, an unknown registry version is refused (exit 2), and the run is idempotent. Image default is report-only (warn);offis silent.Verification
--checkagainst the live state volume: reports 3 missing directories and the 8 recoverable chats, exit 1, writes nothing (registry md5 unchanged).--allagainst a copy of it: exit 0, Validation 35 → 35, App Development 1 → 4, Sondra 1 → 6, one backup, second run a no-op.workspaceDomainState/workspaceRecordzod schemas and satisfies the startup invariants (registry order ↔ table, one owner per session).warn/repair/offbranches tested withsh.Note
This repairs grouping, not files: a recreated directory is empty, because the files lived in the container. Keeping project data under
/workspaceprevents the situation entirely, which the README now states as an image-contract rule. The same PR adds the doctor to the CI recipe hash and smoke test so the next built image carries it.Authored by the dsh agent on the maintainer's behalf; review as you would any agent change.
c4085bbb59toc41d7fb403