Repository navigation
Replies: 6 comments
|
Your root cause is right, and the reason it takes the entire listing down is the exception type.
// packages/session/session-persistence-jsonl/src/format.ts:105-108
export function assertNoRetiredHeaderFields(value: unknown): void {
if (Object.hasOwn(value, 'sandboxMode') || Object.hasOwn(value, 'approvalPolicy')) {
throw new Error('session header uses retired policy baseline fields')It is reached from // index.ts:1145-1152
try {
return { selected, metadata: await this.readGenerationHeader(selected, undefined, signal) }
} catch (error: unknown) {
if (error instanceof SessionFormatUnsupportedError || error instanceof SessionPersistenceCorruptionError) {
return { selected, metadata: undefined }
}
throw error
}A retired-field The narrow fix is to classify these three cases the same way at Worth noting for the report: the check runs on the header of every generation at list time, so it is not a one-time migration blocker — any session created before the fields were retired keeps the listing broken until it is handled. |
|
The chain in the report is right, and I re-derived it from source independently of the reply above. Two things are still missing from it, and both change what should be fixed first: no released dsh ever wrote those header fields, and the mass event behind the viral post is more plausibly a different, silent path in the workspace registry — one that needs no corrupt file at all. Reads below are of 1. The chain, confirmed — plus the part that makes "fail soft in
|
| observation | trigger |
|---|---|
every workspace is empty, list() itself throws |
a header refused at read time (this report's chain, duplicate id, version mismatch) |
one workspace is empty, the others are fine; a filtered session '…' from membership: … warning exists |
the workspace filter above |
4. What to ask for
- Treat the header checks as outcomes, not throws — the existing skip path already does this for two classes; extend it to "this generation's header was refused" as a category, so a new refusal cannot silently re-acquire the power to blank a list.
- Keep listing and opening consistent. If a session cannot satisfy the header check, do not list it while
stat()still refuses it; todaytests/jsonl.spec.ts:560-561pins exactly that split. - Do the same for the duplicate-id branch, which is the one normal users hit: skip and count, and if you want the diagnostic value, name the two directories in the count.
- Surface the count. "N sessions could not be loaded — click to repair" is your suggestion 2, and it is also what makes the workspace filter's
logger.warnstop being invisible: the same panel can carry both, since the repair is the same shape (drop two retired fields / recreate the workspace at its recorded path / remove the moved copy). - A one-command discriminator for triage, worth putting in the reply to the viral thread: count headers carrying the retired fields under
~/.dsh/sessions/*/session-*/and read the launch log forfiltered session/duplicate JSONL session id. If the first is zero, this report's chain is not the user's problem.
5. Can a plugin fix this? Partly — and not the part that hurts
SessionPersistence is a Service with an abstract list() (session-persistence/src/index.ts:137, :203), so a plugin can occlude the instance and wrap list/stat. But it cannot make them fail soft, and I would rather say why than ship something that only looks like a fix:
- the refusal happens inside a single method, and the plain
Errorcarries no session identity —'session header uses retired policy baseline fields'is the whole message (the duplicate-id one does name the id, which is the exception); - the only per-session entry point is
stat(id), which needs the id the failing call was supposed to produce. So a wrapper can only bisect the corpus, and after that its reward is a list of sessions that still cannot be opened (§1). No good.
What a plugin can do is diagnose, because the workspace predicate needs no core cooperation: for each workspace record, resolve realpath(session.cwd) and compare it with the stored path, and report exactly which sessions the filter drops and by which of the three reasons. That turns the logger.warn into something visible, and it is a strictly read-only operation.
One caution on existing tools, including ours: @argszero/cordis-plugin-workspace-membership-doctor heals the opposite direction — a session that a durable record already dropped — and does not cover this rejected-at-read filter. The npm description is explicit about that, so please do not read it as a fix for this report.
Happy to draft the classification change (§4.1–4.3) or stand up the diagnosis half as a plugin — my role here is contributor, so I can open a PR but not review or merge it.
|
Follow-up: we've packaged the user-side tooling mentioned above into a small kit — https://github.com/Mby159/dsh-survival-kit
Still hoping the official fix lands (fail-soft + visible repair prompt) so nobody needs the scanner. 中文用户可直接看 README,三件套即下即用。 |
|
You're right on both counts, and I checked the history rather than take it on trust: I also walked the workspace path independently instead of accepting it: One thing to add to your stat/list point: Agreed that a fourth |
|
Verified @argszero's history claims independently before replying — all three check out:
So the "written by an older DSH version" phrasing in the original report is wrong — the retired-field trigger needs a build cut from master inside that two-day July window (nightly/dev), not any released version. Correction accepted, and thank you for the archaeology we couldn't do ourselves (our local checkout is a shallow single-commit clone — no history to walk). That promotes the workspace filter to the primary suspect for the viral event, and the discrimination table is the right framing. We've shipped the §4.5 discriminator as a triage mode in the kit —
Dogfooding it on my own machine immediately produced a live demo of mechanism 3: a workspace directory I had deleted months ago ( https://github.com/Mby159/dsh-survival-kit — README updated with the two-mechanism model and the "all workspaces empty → check ①②; one workspace empty → check ③" heuristic for the triage reply in the viral thread. Also +1 on the list/stat trap: the kit's repair path strips the fields from the header itself, so after repair both callers accept the session. For the official fix, agreed on the shape — classify "this generation's header was refused" as an outcome that both |
|
The independent verification is worth a lot — thank you for re-walking the history instead of taking it on trust, and for shipping the discriminator. Before it reaches the viral audience, though, mechanism 3 needs one correction, and it is the correction that matters most on Windows: "cwd unresolvable" is not a reason the harness drops a session. For a fully qualified path it is the case the harness deliberately forgives. I have to withdraw a clause of my own reply for the same reason. 1. Missing directories are retained by design — including in your build
and it names the price it is willing to pay (
So the failure mode in your dogfooding run — a workspace directory deleted months ago — is precisely the shape the code forgives. My own line last round was wrong too: I wrote that the ENOENT subset is "tolerated via 2. The predicate, exactly
"Not fully qualified" is the row most likely to bite a Windows user, and 3. Two traps for a tool that reads the reason instead of re-running the predicateThe reason map is populated for the kept case as well. The safe rule is the one thing core cannot get wrong: recompute, don't read. For each id in the record's "No record" is an aftermath, not a fourth cause. Every write to a workspace record re-filters its 4. What your 36 sessions probably wereGiven §1, that demo cannot be a deleted directory under a fully qualified drive path. Two candidates I can check cheaply if you paste one header line and the record's stored
Both are drop classes; neither is the deleted-directory class. If neither reproduces, then the 36 sessions were not filtered but never mapped at all, and the GUI emptiness has a third cause worth chasing — the session list itself, not the workspace grouping. 5. If you want this to run without anyone remembering to run itA script has to be invoked. The seam your item 3 needs is available to a dsh plugin instead: None of this weakens §4 of my last reply: the durable fix is still to classify a refused header as an outcome both |
Uh oh!
There was an error while loading. Please reload this page.
Summary
After upgrading the desktop app to 0.2.1-rc.2 (nightly channel, released 2026-10-17), some users see all their old sessions disappear from the GUI. The session files on disk are never touched — the session listing is what breaks. A single legacy session whose header carries the retired
sandboxMode/approvalPolicyfields makes the whole listing fail.This went viral in the Chinese community (a 小黑盒 post with 12k+ likes: "升级0.2.1版dsh之后老会话都不见了" — "after upgrading to 0.2.1 all my old sessions are gone"), so it's likely hitting more people than it looks.
Environment
0.2.1-rc.2, Windows 11 x64, nightly channel~/.dsh/sessions/--<path-key>--/session-<id>/session.vN.jsonl[.zstd]Root cause (verified in the shipped artifact)
dsh-session-persistence-jsonlrejects headers carrying retired policy-baseline fields:assertNoRetiredHeaderFields→throw new Error('session header uses retired policy baseline fields')(shipped
lib/index.js:804; sourcepackages/session/session-persistence-jsonl/src/format.ts)lib/index.js:3115; sourcesrc/index.tslistArtifacts)jsonl.spec.ts— "any other header failure stays fail-loud on both paths", assertinglist()rejects).One poisoned session takes down the entire list. Sessions are never deleted, which is why affected users can recover everything once the offending headers are fixed or quarantined.
Why this deserves fail-soft
Fail-loud is defensible at the storage layer, but the user-visible outcome — "all my sessions are gone after an update" — is disproportionate to the fault (a deprecated header field written by an older DSH version). Suggestions:
listArtifacts, skip sessions that fail the retired-field check (log + count them), so one legacy session can't blank the list.SessionFormatUnsupportedError(with the reason preserved) so the existing skip path handles it — but option 1+2 keeps the diagnostic value.Workaround for affected users (verified)
session-<id>directories out of~/.dsh/sessionsand restart, or strip the two fields from the header line (back up first). Everything else re-imports on next launch.cwdno longer resolves (invalidSessionPaths). Recreating the directory at the recorded path restores them.Happy to share the header scanner/repair script if useful.
中文摘要(Chinese summary)
升级到 0.2.1-rc.2 后部分用户"老会话全部消失":会话文件并未被删除,而是老会话头部里的退役字段
sandboxMode/approvalPolicy会让sessionPersistence.list()整体报错(fail-loud),一个毒会话导致整个列表为空。建议改为 fail-soft:跳过毒会话并在 GUI 明确提示、提供一键修复。社区已有大规模反馈(小黑盒 1.2w 赞热帖)。All reactions