Session resume fails: Host Cordis inspect provider "Service" is already registered (process-singleton registry collides between preset mounts) #1415
Replies: 6 comments 7 replies
|
Great write-up — the source-level analysis is clear and the repro is well scoped. This is the sibling of a bug I filed earlier today (#1404): the same plugin being mounted twice in one host process isn't handled idempotently anywhere in the stack.
Same theme, different layer — yours is the runtime/service layer, mine is the composition layer. If a maintainer picks up either one, the other is worth checking for the same class of fix (idempotent registration / dedupe vs. preventing the duplicate mount in the first place). On your workaround: the catch-and-skip patch is reasonable, and your fix suggestions look right — I'd personally lean toward making About where to submit the PR: heads-up that per the repo's CONTRIBUTING.md the project currently does not accept external pull requests, and GitHub Issues are disabled — Discussions (this post) is the supported channel, and the team says they monitor and consider them when allocating resources. Keeping your patch ready in a fork/gist and linking it here is probably the most useful next step until that changes. Really solid report — thanks for writing it up so thoroughly. |
|
Follow-up: recovery verified Confirmed the recovery path works: closing the live session on the locally authored preset (normal teardown) released the four provider ids — the effect disposers run on clean session teardown — and the stuck This also confirms the leak only happens on abnormal teardown (e.g. a turn interrupted by a command error), which is exactly the "leftover registration" shape described above: clean close → ids freed; abnormal end → ids stay until the process restarts. One more data point: while the holder was live I also tried hot-patching the singleton's Operational rule for anyone hitting this: in one host process, open the |
|
Independent verification — I reproduced the core mechanism against the real packages (
The disposer wiring is exactly as you described: One small nuance on your sandbox note: the block isn't quite "silent" — the sandbox ctx's The only thing I couldn't reproduce end-to-end is the full session-resume failure itself (needs the real preset/session machinery), but the mechanism chain is fully confirmed, so the outcome follows. Thanks again for the thorough write-up — this one is now solidly documented. |
Additional reproduction from a second environment (Windows, web profile)Same root cause, one more trigger path worth adding to the report: Trigger: editing a preset then creating a new session in the same host process. Evidence that the failure is at preset mount, not session storage:
Impact on the operator: once a process hosts one cordis-based session, every Additional suggestion on the fix, on top of the ones already listed: Environment: DSH 0.1.0-rc.6 (latest on npm as of 2026-08-14), Windows 11, web profile, Happy to provide logs / session excerpts on request. Thanks for the thorough write-up! Local patch we applied (works in production, verified)We patched the registry itself so duplicate-id registration is idempotent instead File: Before (upstream): register(registration) {
const manifest = validateManifest(registration.manifest);
if (this.providers.has(manifest.id))
throw new Error(`Host Cordis inspect provider "${manifest.id}" is already registered`);
const stored = { ...registration, manifest };
this.providers.set(manifest.id, stored);
return () => {
if (this.providers.get(manifest.id) === stored)
this.providers.delete(manifest.id);
};
}After (local patch, marked register(registration) {
const manifest = validateManifest(registration.manifest);
if (this.providers.has(manifest.id)) {
// Idempotent: a second cordis-based session mounting the same static
// first-party providers reuses the existing registration.
console.warn(`[dsh-local-patch] Host Cordis inspect provider "${manifest.id}" is already registered; reusing existing registration (idempotent)`);
return () => { };
}
const stored = { ...registration, manifest };
this.providers.set(manifest.id, stored);
return () => {
if (this.providers.get(manifest.id) === stored)
this.providers.delete(manifest.id);
};
}Why it is safe: the four first-party providers ( Verification: after restarting the host process, a duplicate Note on Windows installs: |
|
Session resume 撞上 Service 已注册——预设挂载的进程单例冲突(Cordis toolset 被重复注册)。 和第 3 章"依赖/挂载解析"同类:多预设叠加时单例服务冲突。排查思路(哪个 preset 重复挂了 Cordis toolset)见第 3 章:https://github.com/Electricitysheep/dsh-handbook/blob/main/docs/03-profiles.md |
|
Thanks for the second-environment reproduction — the new trigger path (edit preset → create session) is a useful extension: it shows the collision fires on any remount of |
Uh oh!
There was an error while loading. Please reload this page.
Describe the bug
Resuming a session whose agent preset includes the Cordis toolset fails with:
Once this happens the conversation can no longer be reopened at all — every prompt attempt fails the same way. The session's stored data is intact (it is a mount-time failure only, not data corruption).
Environment
cordispreset, one on a locally authored preset that also mounts a tool-cordis rowRoot cause (from source)
CordisInspectRegistryServiceis a process-global singleton created byDynamicCordisRunnerServiceon the host plane (new CordisInspectRegistryService(ctx), service keycordisInspect, in@deepseek-ai/dsh-cordis-host-runner). Itsregister()throwsHost Cordis inspect provider "<id>" is already registeredwhen the provider id already exists in itsMap.@deepseek-ai/dsh-tool-cordisregisters the first-party Host inspect providers (Service,Event,Builtin,Tool) unconditionally inapply():cordispreset and any locally authored preset that copies it) collide: whichever mounts second throws, which fails the whole preset mount, which fails the session resume. The failure is order-dependent: the first mount wins and holds the ids until its fiber is disposed.In my case it appeared in two shapes. First, a session on the shipped cordis preset that was torn down abnormally (a command error interrupted its last turn) left its four provider ids behind in the process-global registry, so every subsequent resume of that same session collided with its own leftover registration. Second, after a host restart, resuming the same session while a locally authored preset (a patched copy of dsh-tool-cordis) was already live failed identically, because the vanilla package threw on Service. Either way the conversation stays unrecoverable until the holder goes away or the process restarts.
Workaround I use locally
I patched a copy of
dsh-tool-cordiswhose registration loop catches the duplicate error and skips it (the providers are identical, so sharing one registration is correct):This makes the patched preset tolerant when it mounts second, but the vanilla
cordispreset still fails when it mounts after another tool-cordis instance — the collision is only fixed in one direction.Suggested fixes
CordisInspectRegistryService.register()idempotent for duplicate ids (reference-counted, or replace-and-return-disposer).Although I’m still a university student, if any of the experts or maintainers could tell me exactly where to submit it, I’d like to submit a PR.
Wishing you all the best! Thank you!
All reactions