Replies: 5 comments 1 reply
|
Patch for the two fixes that matter for this report: the readiness gate, and never consuming a document the composition could not import. It applies as-is to What changes (
The rest of the patch is the three tests and the two READMEs (with the bilingual pairing record re-recorded). Verification
The tests cover a startup that never commits readiness, a startup disposed while the pending import waits, and a host with no readiness signal at all. Full patch (diff against
|
|
I reproduced your version claims independently, verified the mechanism anchor by anchor, and read the patch. The diagnosis is exact and the readiness gate is the right fix. Two things I would want closed before it lands, both supported by code already in the tree — one of them decides whether the fix is complete or conditional. Your version claims, independently confirmed
Mechanism anchors all hold at HEAD: the one-shot continuation (§1) is 1. The fallback branch is where the loss survives, and the file next door already decided differentlyYour fallback — no
So the choice is already made once in this family, and the fallback is the one place where an absent readiness signal silently keeps the bug. It is also why your third test reads the way it does — 2. The rename is still not a postcondition — the guard is a single instantYour suggestion 2 is not implemented by the patch. Order today is: read → That window is reachable from the event your own timeline already contains — the The small delta that closes the class: keep the gate, then move the rename after the loop (flush-to-imported, not rename-then-import). The objection you might expect — "a partial import would then repeat" — does not apply here, because One smaller point in the same area: with the counters in place, Prevention is core; recovery is mountable, and it is what your workaround did by handPrevention is not a plugin surface: the import is scheduled inside the Recovery is a different story, and it maps onto what you had to do manually. One boundary worth naming, since it decides how clean that plugin can be: Thanks — this is the clearest write-up I have read of the 0.1.7 upgrade path; the timeline and the "same settled Loader" framing are what made it possible to locate the window precisely. |
|
Hi, Dear @argszero Could you tell me whether this fix will be in the next release, or whether it should be replaced with something more correct? |
|
Additional macOS field report; this may overlap the migration issue, but I cannot attribute the writer to Desktop from the available evidence.
The user-visible failure is that shared Harness state can appear wiped after using the Desktop and CLI installations, with no migration or recovery warning. Expected behavior: preserve a last-known-good backup, migrate using an atomic write only after the destination accepts the entries, surface rejected sections in the UI, and never silently replace recoverable settings with an empty document. Could you identify which migration or write path can leave settings.yaml empty in this version combination? |
|
我这个case和settings.yaml一次性导入被吞掉不是同一个机制,但在"配置看着没了"这个现象上撞了墙,供参考。 我的情况是跨版本抄配置。web 侧 runtime 是 rc.1,Desktop 内嵌是 rc.2,我把 web profile 的 cordis.patch.yml 整份搬给 Desktop,结构上六个 provider 都在,用官方 dump-config 加载也能正常组合,但 Desktop 设置页只显示内置的 DeepSeek。读源码确认设置页对 provider 完全不做过滤,llm-pi-ai 一挂载就把整份目录注册成可配置项,所以页面空说明插件没挂上而不是没填密钥。 顺带记一个 YAML 的坑。YAML 1.1 会把裸键 off 解析成布尔 false,再序列化就写成 false:。llm-pi-ai 的 resolveModelReasoning 只遍历 THINKING_LEVELS 取值,多出来的 "false" 键既不报错也不告警,直接被忽略,于是模型的关闭思考档凭空消失,其余档位照常工作,肉眼很难发现。判断标准是跑 dump-config,键正确时输出带引号显示 'off': none,被污染时显示 false: none。这条跟版本无关,任何一次配置重写都可能踩到。 |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Summary
Upgrading
0.1.6-alpha.2→0.1.7-alpha.1, the first launch consumed~/.dsh/settings.yamlalthough that launch had failed its startup audit.@deepseek-ai/dsh-app-bootdisposes the plugin tree when the audit rejects a required entry, and the one-shot legacy import is a continuation of the same loader-settled moment, so all 10 sections were written into an already-disposed context:The document is renamed to
settings.yaml.importedbefore the first write (index.ts:245-246, described inpackages/settings/settings/README.md:35), and the import returns early wheneversettings.yamlis absent (index.ts:244), so no later launch retries. The operator-visible result: the settings page comes up with defaults — LLM providers, theme, permission preset, default agent preset, default model, subagent model selection all reverted — and the only surviving copy of those values is the renamed file.This matters beyond one deployment because the import path is new in this release (
601d6761e4, "feat(settings): project volatile Config through profile-backed forms (#4587)", contained indsh-v0.1.7-alpha.1): every user upgrading from ≤0.1.6 goes through it exactly once. The same upgrade also makes a failing first launch likely, since 0.1.7 refuses to activate entries whose artifacts are stale (web-appfrontend dist,client-modulesbundles), and the documented upgrade step is a rebuild — one launch before the rebuild finishes is enough to lose the settings. The import path is unchanged indsh-v0.1.7-alpha.2: between the two tags this package differs only in itspackage.json, so the failure reproduces there as well.Environment
dsh 0.1.7-alpha.1(c36a83ff6b), source launch, macOS 15 (arm64), Node v24.18.0web; its patch enables HMR (- id: hmr,disabled: false)dsh 0.1.7-alpha.2(00102833df): the settings package differs between alpha.1 and alpha.2 only in itspackage.json, so this reproduces there too$HOME/.dsh/settings.yamlleft by0.1.6-alpha.2, 10 sections:ui-onboarding,ui-theme,agent-presets,permission,agent-default-model,llm-pi-ai,provider-quotas,dsh-better-sidebar,glance-theme,subagent-model-selectionTimeline
From the startup diagnostics dump of that launch (
$HOME/.dsh/logs/startup-2026-09-22T14-06-35.304Z-<uuid>.log):settings: imported %s into profile %s(index.ts:257) appears in no log of any launch.Root cause
packages/settings/settings/src/index.ts:235— the import is a one-shot continuation of Loader settlement:void ctx.root.loader.await().then(() => this.importLegacyDocument()).catch((error) => { ctx.logger.error(error) }).packages/boot/app-boot/src/index.ts:971-977— the startup audit is a continuation of the same moment:await ctx.get('loader')?.await(), thenawait auditStartupEntries(ctx, binName)throwsStartupErrorfor inactive required entries, and the catch disposes the tree withawait ctx.fiber.dispose(). On a failed launch, the import and the disposal of its own fiber therefore start from the same settled Loader.index.ts:382— every write resolvesthis.ownerContext.configEditoron the context captured in the constructor. After disposal that iscannot get required service "configEditor" in inactive context(vendor/cordis/src/reflect.ts:160).index.ts:248-256— the per-sectiontry/catchdowngrades that to a warning and the loop continues; the samecatchis the intended path for "the running composition rejects this section" (README:35), so a dead service and a rejected section are indistinguishable here.index.ts:245-246renames the document before any write — deliberate, so that a partial import never repeats;index.ts:244then skips everything on later launches becausesettings.yamlno longer exists. The values survive only insettings.yaml.imported, and nothing tells the operator.this.closedalready exists (set by the effect disposer,index.ts:227) butimportLegacyDocumentnever consults it, so the service keeps writing after its own disposal. Note also thatindex.ts:257logssettings: imported …unconditionally after the loop: a launch that imported nothing can still report success.The
hmr: config reload … failed/HMR is disposedpair in the same 10 ms window is a symptom of that teardown (closingis only set when the HMR service itself is disposed), not a second cause; a profile recomposition during the same window would fail in exactly the same way.Minimal reproduction
Suggested fixes
importLegacyDocumentfrom application readiness (appReady/ theapp-boot/config-reloadpath) rather than fromloader.await(); a launch that failed its audit then leaves the document untouched for the next launch.this.closed/ context liveness before each write and abort the loop with an error that keeps the document, instead of 10 warnings and a success line.settings: 0 of N sections imported; values remain in <path>.imported), so providers do not vanish without a trace.Our workaround (no data was lost)
We restored all restorable sections by hand: each one became a config override row in
$HOME/.dsh/profiles/web/cordis.patch.yml(- id: <entry id>plusconfig: …), validated against the 0.1.7 Config schemas;provider-quotasneeded no restore because that plugin now keeps its own storage. Two sections could not be restored by configuration at all and are third-party plugin work:glance-theme(the plugin still calls the removedctx.settings.register) anddsh-better-sidebar(not mounted any more).Test to extend
packages/settings/settings/tests/configuration.spec.ts:310("imports the removed settings.yaml into the profile once and keeps rejected sections in the renamed file") covers rename-first and rejected sections, but never disposes the settings fiber while the import is in flight. A case that disposes the tree betweenstart()and the first write — or that makesconfigEditorunavailable — would pin the ordering.All reactions