dsh-bundle-dedup-guard
A DeepSeek Harness plugin that catches duplicate loader entry ids in profile bundle lists — and audits the whole plugin environment (vendor tree, plugin junctions, known conflict pairs) — on every plugin load.
If a profile's dsh.profile.bundles lists an aggregate bundle (a bundle whose patch inserts all of its sub-plugins, e.g. @linxin666/dsh-web-ui-all) and its sub-plugins individually, the loader receives the same loader-entry id twice. EntryGroup.update throws duplicate loader entry id: <id> before any plugin starts, and the whole profile fails to boot. This plugin exists so that never happens silently again.
Why this exists
Incident, 2026-08-18: a web profile listed @linxin666/dsh-web-ui-all (which aggregates 13 sub-plugins into one patch) and all 13 sub-plugins separately. Every sub-plugin id was inserted twice; the first collision reported was duplicate loader entry id: ui-dsh-aionui-panel. Fixing only the bundles list was not enough — the dsh plugin command's reconcilePlugins re-appends every dependencies entry that declares dsh.bundle to the bundle list after each pnpm operation, so the sub-plugins came back an hour later and crashed the next boot.
Incident, 2026-08-21: a bulk plugin update wiped plugin node_modules (8 plugins failed to boot with Cannot find package); a repair npm install followed an @deepseek-ai junction into the vendor tree and corrupted it; and once dependencies were restored, dsh-better-sidebar ended up mounted twice (/sidebar/api duplicate-route crash). v0.2.0 turns these red lines into automatic site-level checks — see Site-level health checks below.
Full incident records: docs/KNOWN-ISSUE-bundle-duplicate.md (2026-08-18) and the 2026-08-21 self-repair manual surfaced by the site audit.
How it works
The loader's failure path is: cordis-plugin-include's applyEntryPatches flattens every bundle's insert entries without deduplicating, then cordis-plugin-loader's EntryGroup.update dedups by id and throws on the first duplicate — before any plugin entry is created. This plugin re-implements exactly that "flatten + dedup by id" semantics in pure Node, and reports the offending ids, their sources (which bundle/patch inserted each), and the fix.
Checks on every plugin load
| Trigger | When | Notes |
|---|---|---|
| Apply | every boot | instant health check as the plugin mounts |
| Loader events | loader/entry-init / loader/partial-dispose |
runtime hot loads / plugin additions, debounced 800 ms |
| Manifest watch | fs.watch on the profile dir |
the moment package.json or cordis.patch.yml changes — i.e. dsh plugin add, marketplace installs, or hand edits — warn immediately, before the next restart |
What it reports
- Duplicate loader entry ids — each id inserted by more than one source, with the full source chain (e.g.
ui-dsh-aionui-panel: @linxin666/dsh-web-ui-all ← @linxin666/dsh-client-ui-aionui-panel). - Unresolved bundles — listed in
bundlesbut not resolvable (the loader would loud-fail too). - Bundle-less packages — listed but without a
dsh.bundle.patch(a misconfiguration per the loader contract). - Predictive reconcile warning — a
dependenciesentry that declaresdsh.bundlebut is not inbundles.dsh plugin's reconcile will append it on the next install/update; if it's a sub-plugin covered by an aggregate, that re-creates the crash. The warning names the covered ids. Fix: move such packages todevDependencies(reconcile only readsdependencies).
Reports are written to $DSH_HOME/dsh-bundle-dedup-guard/reports/<profile>-<timestamp>.json and <profile>.latest.json.
Site-level health checks (v0.2.0)
Beyond the bundle-list checks, every run also audits the whole plugin environment,
turning the 2026-08-21 incident's red lines into automatic checks (lib/site-health.mjs,
zero-dependency, read-only):
| Check | Detects | Incident |
|---|---|---|
| Known conflict pairs | bundles lists @linxin666/dsh-web-ui-all and dsh-better-sidebar together — both entries execute the same lib/index.js and register the same /sidebar/api route (duplicate prefix route crash at apply time) |
2026-08-21 |
| Vendor tree integrity | every @deepseek-ai/* package vs resources/vendor/dsh/node_modules/.package-lock.json: missing / empty dir / package.json name mismatch (wrong content installed) / version mismatch; .name-* temp-dir leftovers (informational) |
2026-08-21 |
| Plugin junction integrity | plugins whose runtime code imports @deepseek-ai/* but whose package/node_modules/@deepseek-ai junction is missing (the exact Cannot find package boot crash), points at the wrong target, or dangles; real-dir copies (works, informational) and .npmbak leftovers (an npm install ran inside a junction dir) |
2026-08-21 |
| Incident manual pointer | surfaces the latest $DSH_HOME/incidents/<date>/README.md self-repair manual |
— |
Every problem is reported with a copy-paste fix command (recreate the junction / run
repair-vendor.ps1 / remove the bundles entry). The guard never modifies anything itself.
The junction check decides "does this plugin need a junction" by source-scanning runtime
import/requireof@deepseek-ai/*(not bypackage.jsondeclarations), and only audits plugins actually listed in some profile'sbundles— dormant plugin dirs are skipped.
Known limitation
The loader deduplicates before creating any plugin entry, so when duplicates already exist at boot, an in-process check cannot run — the tree never mounts. For that case use the standalone CLI below: it is pure disk reads and works even when boot is broken.
Installation
As a profile bundle (recommended while in development):
- Add to the profile's
package.jsondependencies:"dsh-bundle-dedup-guard": "link:F:/path/to/dsh-bundle-dedup-guard" - Add
"dsh-bundle-dedup-guard"todsh.profile.bundles(first entry is fine). - Link it into the profile's
node_modules(pnpm does this fordsh plugin add).
From npm:
dsh plugin --profile web add dsh-bundle-dedup-guard
Usage
The plugin checks automatically — no interaction needed. For manual diagnosis (including when boot already crashed):
# check all profiles + site-level health (DSH_HOME defaults to ~/.dsh)
node bin/check.mjs
# a specific profile
node bin/check.mjs --profile web
# a specific manifest file (e.g. a pre-fix backup, for testing)
node bin/check.mjs --manifest <path-to-package.json>
# machine-readable JSON, skip report files
node bin/check.mjs --profile web --json --no-write
# skip the site-level health audit
node bin/check.mjs --no-site
Exit codes: 0 = healthy, 1 = duplicates / unresolved bundles / bundle-less packages /
site-level problems found (useful as a CI gate).
Fixing duplicates
Edit dsh.profile.bundles so each id has exactly one source. The common shape is "aggregate + sub-plugins":
- keep the aggregate (e.g.
@linxin666/dsh-web-ui-all) - remove the individually listed sub-plugin entries
- also move the sub-plugins from
dependenciestodevDependencies— otherwisedsh pluginreconcile re-appends them on the next install/update (the exact recurrence from 2026-08-18)
Then re-run node bin/check.mjs --profile <name> until green, and restart.
Development
npm test # node --test, zero dependencies
npm run check # run the guard against your local profiles
lib/check.mjs— the check core (pure Node, no third-party deps)lib/site-health.mjs— site-level health checks (v0.2.0: conflict pairs, vendor tree, junctions)index.mjs— the Cordis plugin entry (apply+ listeners)bin/check.mjs— standalone CLI (works without a booted tree)test/— unit tests with fixture profiles
License
MIT
No comments yet. Be the first to write one.