DSH plugin: npm ETARGET, or installs but will not boot

TroubleshootingPublished 2026-10-03Author: DeepSeek Plugin Market
DeepSeek HarnessDSHnpmETARGETprereleasedist-taginstall failureboot failure
One npm dist-tag gives two symptoms: installing DSH fails with ETARGET, or dsh web will not boot — a missing rc.3 package while latest stays at rc.2.

In the 0.1.5 prerelease-line release accident, npm install -g @deepseek-ai/dsh presented two completely different symptoms at once: one is cannot install — reporting ETARGET: No matching version found for @deepseek-ai/dsh-client-ui-sidebar-documentpreview@^0.1.5-rc.3; the other is installs but will not boot — dsh web exits 1 reporting plugin(s) failed to load: @deepseek-ai/dsh-sandbox-local, and the server never comes up. The two are in fact two facets of the same npm dist-tag drift: latest still pointed at 0.1.5-rc.2, while its 63 sibling dependencies are all ^0.1.5-rc.2 and get resolved by npm to the newer rc.3, forming a "root rc.2 + siblings rc.3" mixed-generation tree (#7542); at the same time the rc.3 version set shipped one member package short, and a prerelease range is satisfied only by versions with the same major.minor.patch, so ^0.1.5-rc.3 has no solution in the registry (#7436). This article proceeds as "triage first → the two mechanisms → three fixes → outcome and troubleshooting notes", with pasteable registry commands and A/B evidence at every step.

Triage first: cannot install, or installs but will not boot

The criteria for these two symptoms are entirely different — one stops at npm install, the other happens when dsh web boots. Look at which step the error appears in first, then match it.

CriterionCannot install (ETARGET)Installs but will not boot (resolution failure)
Failing commandnpm install -g @deepseek-ai/dshdsh web (install already succeeded)
Error keywordsnpm error code ETARGET / No matching version foundplugin(s) failed to load / could not be resolved
Registry levelsome member package's some version does not existall versions exist, but the dependency tree is mixed-generation
Affected tagsnext fails directly, latest fails transitively via caretlatest (= rc.2) fails, next (= rc.3) is fine
Root-cause sideincomplete release set + prerelease matching rulecaret floating versions + changed hoist layout
Cost at the timebrand-new users cannot installbrand-new users install it, and it will not boot

Two commands to read the registry clearly first

When troubleshooting any "cannot install / installed wrong" problem, the first step is always to read the registry's real state rather than guess:

bash
# 1. Which version each of the three channels points at right now
npm view @deepseek-ai/dsh dist-tags --json

# 2. Which versions a caret range can actually match (can it go forward/backward?)
npm view "@deepseek-ai/dsh-client-ui-sidebar-documentpreview@^0.1.5-rc.3" version

# 3. What sibling dependency ranges the root package declares
npm view @deepseek-ai/dsh@0.1.5-rc.2 dependencies --json

The output on the day of the incident (excerpted from the two reports' measurements):

text
# when broken
$ npm view "@deepseek-ai/dsh-client-ui-sidebar-documentpreview@^0.1.5-rc.3" version
404 No match found for version ^0.1.5-rc.3

# after the fix
$ npm view "@deepseek-ai/dsh-client-ui-sidebar-documentpreview@^0.1.5-rc.3" version
0.1.5-rc.3                     <- now it resolves

@deepseek-ai/dsh dist-tags:
  latest : 0.1.5-rc.3          <- was 0.1.5-rc.2
  next   : 0.1.5-rc.3
  alpha  : 0.1.7-alpha.2

Mechanism one: a prerelease range cannot "go forward", so a missing member package is a hard failure

In one sentence: the range ^0.1.5-rc.3 is satisfied only by prereleases with the same major.minor.patch, like 0.1.5-rc.3 / 0.1.5-rc.4; once rc.3 is not published, neither 0.1.6-alpha.2 nor 0.1.7-alpha.1 can rescue it, so there is no solution in the registry and ETARGET is the correct result.

The missing member package

The 0.1.5-rc.3 release set was published at 2026-09-22T05:55Z, but @deepseek-ai/dsh-client-ui-sidebar-documentpreview is not in that set at all — its own next tag still pointed at 0.1.5-rc.2 at the time, while dsh and dsh-web-app had both moved next to rc.3. The available documentpreview versions were:

text
0.1.5-alpha.2, 0.1.5-rc.1, 0.1.5-rc.2, 0.1.6-alpha.1, 0.1.6-alpha.2, 0.1.7-alpha.1

There is no 0.1.5-rc.3. And @deepseek-ai/dsh-web-app@0.1.5-rc.3 declared @deepseek-ai/dsh-client-ui-sidebar-documentpreview@^0.1.5-rc.3, so it breaks the moment you install it.

Why there is "no way back, no way in"

This is an explicit semver rule, and a control experiment with the same package makes it clearest:

text
$ npm view "@deepseek-ai/dsh-web-app@^0.1.6-alpha.1" version
@deepseek-ai/dsh-web-app@0.1.6-alpha.1 '0.1.6-alpha.1'
@deepseek-ai/dsh-web-app@0.1.6-alpha.2 '0.1.6-alpha.2'

Note that 0.1.7-alpha.1 exists but is not in the list. Likewise, ^0.1.5-rc.3 will not be satisfied by 0.1.6-alpha.2 or 0.1.7-alpha.1, nor will it fall back to 0.1.5-rc.2. The rule "a prerelease range matches only prereleases with the same major.minor.patch" turns this kind of release accident from "partial downgrade" straight into "hard failure" — with no version to fall back to, npm can only report ETARGET.

Why latest goes down with it

dsh@0.1.5-rc.2 (the latest at the time) declared @deepseek-ai/dsh-web-app: ^0.1.5-rc.2, and that range matches both rc.2 and rc.3:

text
$ npm view "@deepseek-ai/dsh-web-app@^0.1.5-rc.2" version
@deepseek-ai/dsh-web-app@0.1.5-rc.2 '0.1.5-rc.2'
@deepseek-ai/dsh-web-app@0.1.5-rc.3 '0.1.5-rc.3'

npm takes the highest, rc.3, so latest gets dragged into the same broken chain. One member package shipped short, and next fails directly while latest fails transitively via caret — that is why even the default install command was unusable at the time.

Why CI did not catch it

The release script scripts/release/publish.ts publishes a packing directory in recorded order, and its self-described integrity contract is that "every entry in the order settles as either published or already present" (:143-145), with a real integrity check for the "already present" branch (:155-166). But nowhere in the whole flow does anything ask: can the ranges declared by the entries just published actually be resolved on the registry? A post-check (resolving every in-set range the way the two npm view commands above do) would have caught it before latest was dragged down — and that is exactly the step that makes it visible to users.

Mechanism two: with latest at rc.2, 63 sibling dependencies float to rc.3, forming a mixed-generation tree

In one sentence: dsh@0.1.5-rc.2's 63 sibling dependencies are all ^0.1.5-rc.2, which resolve to the higher rc.3; so in a "root rc.2, siblings rc.3" tree, @deepseek-ai/dsh-sandbox-local gets nested two levels down, and at boot one resolution is anchored on the app install directory, giving a straight MODULE_NOT_FOUND.

Two root versions, two layouts

dsh@0.1.5-rc.2 (the then-latest)dsh@0.1.5-rc.3 (the then-next)
Boot (clean DSH_HOME)exit 1: plugin tree failed to load: @deepseek-ai/dsh-sandbox-local ... could not be resolved, 19 lines of stderr, no URLalive after 35 seconds, empty stderr, prints dsh web: http://127.0.0.1:<port>/?token=…
Where dsh-sandbox-local lands.../dsh/node_modules/@deepseek-ai/**dsh-base**/node_modules/@deepseek-ai/dsh-sandbox-local (plus another copy under dsh-sdk-minimal).../dsh/node_modules/@deepseek-ai/dsh-sandbox-local (right beside the app)
Installed package count563518

Same machine, same npm, same Node, a few minutes apart, the only variable is the root version — fully consistent with another community's runner A/B:

root packageubuntu-latestwindows-latest
0.1.5-rc.3bootsboots
0.1.5-rc.2 (= the then-latest)plugin(s) failed to load: dsh-sandbox-localsame failure
0.1.2-rc.1will not boot (a different failure)will not boot

Three probes: what fails is the "app install anchor", not the profile directory

This is the most crucial step in the whole thread — it resolves two seemingly contradictory statements:

Proberc.2rc.3
Resolve @deepseek-ai/dsh-sandbox-local from the app install directory (<prefix>/@deepseek-ai/dsh/lib)MODULE_NOT_FOUNDOK
The projection link in the profile directorypresent, but pointing two levels deeppresent, pointing at the copy beside the app
Resolve from the profile directoryOKOK

In other words: resolution from the profile directory works (which is why "just probe from the profile directory" never reveals this failure), while the resolution whose failure actually breaks the boot is anchored on the app install directory, which in rc.2's mixed tree cannot reach dsh-sandbox-local. In rc.3 that package is right beside the app, so the same anchor succeeds. "The mixed tree changes the hoist layout" and "resolution can succeed" are therefore both true — they just refer to different anchors.

The refuted hypothesis: pnpm is not a boot prerequisite

At one point the thread popularized "a clean machine has no pnpm, so the profile was never prepared". A controlled experiment ruled it out: rc.3 boots fine on a clean runner without pnpm; and there is no pnpm spawn on the boot path either — apps/cli/src/plugin.ts only reports pnpm was not found for dsh plugin … (the only CLI consumer that forwards its arguments to pnpm), and initProfile merely writes the profile's pnpm-workspace.yaml.

But note the distinction: pnpm is not a "boot" prerequisite, yet it is a "plugin management" prerequisite (dsh plugin … forwards to pnpm). So installing pnpm on a fresh machine is still good practice, just do not treat it as the root cause of a boot failure.

Ruling out another explanation: the install script is not a variable

A controlled comparison on the same Windows machine, the same day, installing the same dsh version five times:

#How @deepseek-ai/dsh@0.1.5-rc.2 was installedSibling packages resolved toCopies of dsh-win32-processdsh --profile headless
1npx cache built before rc.3 was published231 × rc.2, 0 duplicates1boots and responds
2npm install -g (default, skips install scripts)1 × rc.2 root + 270 × rc.3, 28 duplicated packages4fails
3npm install -g --allow-scripts=…same as #24fails
4installed into an empty local project1 × rc.2 root + 230 × rc.3, but flat hoist, 0 duplicates1boots and responds
5npx -y @deepseek-ai/dsh@alpha267 × alpha.2, 0 duplicates1boots and responds

The reading is clean: #2 vs #3 shows the install script is not a variable; #2 vs #4 shows the same mixed rc.2/rc.3 graph boots when flatly hoisted but fails when dsh itself is the tree root; #1 vs #2 is the timeline — an rc.2 installed before rc.3 existed was self-consistent and worked. "Who is the tree root" and "is the layout flat" are the decisive differences.

Three fixes

Fix one (available to users right now): pin by generation, do not install by tag

Idea: make the whole tree one generation, instead of making one package accommodate the rest. The root of the mixed-generation tree is "root rc.2 + siblings rc.3", so pinning the entry to the same generation as the siblings suffices:

bash
# Option A: install next directly (the then = 0.1.5-rc.3), the simplest and most effective route
npm uninstall -g @deepseek-ai/dsh
npm install -g @deepseek-ai/dsh@next

# Option B: pin the version explicitly
npm i -g @deepseek-ai/dsh@0.1.5-rc.3

# While you are at it, install pnpm for plugin management (not a boot prerequisite, but dsh plugin needs it)
npm i -g pnpm

Two caveats:

  1. "Pin, do not force in one package": adding @deepseek-ai/dsh-sandbox-local to the dependencies alone only moves one package, while the failure comes from the whole set being inconsistent; pinning the entry to the same generation is what stabilizes the layout. If you insist on staying on rc.2, you would have to pin all 63 sibling dependencies — the same thing, just more typing.
  2. pnpm is a separate half: it cannot fix resolution, but plugin management needs it; installing it first on a fresh machine removes one category of "Cannot find package" noise.

Fix two (the ETARGET case): switch channel or use a local override

If what you hit was "cannot install", the two bypasses available at the time were:

bash
# 1. Switch to the alpha channel (the then = 0.1.7-alpha.1, whose dsh-web-app / documentpreview ranges are self-consistent)
npm install -g @deepseek-ai/dsh@alpha
jsonc
// 2. In a local project, use overrides to pin the broken link back to the old version
{
  "overrides": {
    "@deepseek-ai/dsh-web-app": "0.1.5-rc.2"
  }
}

alpha works because 0.1.7-alpha.1's dsh-web-app is 0.1.7-alpha.1 and its documentpreview range ^0.1.7-alpha.1 resolves — the whole chain is self-consistent, which is exactly why it is reliable.

Fix three (upstream): move the tag, pin ranges precisely, add direct dependencies, add diagnostics

Upstream fixes targeting the mechanism, in order of importance:

  1. Move latest to a self-consistent generation (0.1.5-rc.3 boots), or change 0.1.5-rc.2's dependencies to exact versions — the current ^0.1.5-rc.2 allowing a newer prerelease is precisely what created the mixed tree. "Pinning the range" is the structurally more important half: as long as @deepseek-ai/dsh@X declares ^X for the whole family, one install can take some packages from each of two release generations, and the layout that was packed and probed at release time is not the same layout the user actually gets.
  2. Declare @deepseek-ai/dsh-sandbox-local as a direct dependency of dsh, so npm hoists it to the top level no matter how the other packages are arranged. This is harmless but not a cure: in a mixed tree this package, even nested, can be resolved from the profile directory, and the same silent skipping applies to every missing package — it makes the tree more uniform but does not make resolution more correct.
  3. Add a post-release check: in scripts/release/publish.ts, resolve the declared ranges of the just-published set one by one (using the two npm view commands from this article), catching it before latest is dragged down.
  4. Say out loud which dependencies were silently skipped: the closure walk silently continues for a dependency that is "declared but not found at any anchor" —
ts
const dir = packageDirFromAnchor(next.anchor, dep)
// A declared-but-uninstalled dependency cannot be a loader-visible
// plugin; skip it rather than fail the whole boot.
if (dir === undefined) continue

(packages/boot/app-boot/src/profile.ts:494-496) so the Loader reports only that entry as unresolvable, without saying which package or which manifest declared it. The resolution process already records each package's declarer (master's createRuntimeResolution keeps declarers and versions maps), so printing one line of "what was skipped, declared by whom" turns "incomprehensible" into a five-minute diagnosis. The reporter points out specifically that the summary at the time said "see the error(s) logged above", yet there were no such lines above it — stderr had only the summary and a stack. This diagnostic gap is real.

Outcome: both fixes are already live

The conclusion first: this pit has been filled in, and latest has moved from rc.2 to rc.3, curing both "cannot install" and "will not boot" at once.

text
$ npm view "@deepseek-ai/dsh-client-ui-sidebar-documentpreview@^0.1.5-rc.3" version
0.1.5-rc.3                     <- previously "404 No match found"

@deepseek-ai/dsh dist-tags:
  latest : 0.1.5-rc.3          <- was 0.1.5-rc.2
  next   : 0.1.5-rc.3
  alpha  : 0.1.7-alpha.2

That is: the missing member package was published, and latest was moved onto it (exactly the first of the two options listed in the original report). An ordinary npm install -g @deepseek-ai/dsh should resolve again; old machines still pinned at rc.2 just need a reinstall.

But the two lessons at the mechanism level still hold, because they describe a "class" rather than "this time":

  1. The failure is hard rather than graceful, because of the prerelease matching rule: ^0.1.5-rc.3 cannot be satisfied by 0.1.6-alpha.2 or 0.1.7-alpha.1 — a prerelease range matches only prereleases with the same major.minor.patch. Publishing that member package cured this instance but removed no reason for "the next one to be equally silent".
  2. The recommended guardrail is still absent: the release tool's self-described integrity contract is still "Every entry in the order settles as either published or already present" (scripts/release/publish.ts:143-145), and there is no step that resolves the declared ranges of the just-published entries. Catching it before the tag moves and makes it user-visible remains the cheapest approach.

Troubleshooting notes

First principle: distinguish "cannot install" from "installs but will not boot" first, then read the registry — do not reinstall, do not delete ~/.dsh, do not suspect third-party plugins first. Both root causes live in DSH's own dependencies.

  1. Read dist-tags first: npm view @deepseek-ai/dsh dist-tags --json. Where latest and next point directly determines which symptom you hit (#7542).
  2. Do not explain a boot failure with "is pnpm installed": there is no pnpm spawn on the boot path; rc.3 boots fine on a clean runner without pnpm. pnpm only affects dsh plugin … (#7542).
  3. Do not explain it with "the install script did not run": --allow-scripts and a default install get the same mixed graph and fail the same way; npx @alpha skips scripts and still boots (#7542).
  4. Run the three probes before concluding: (1) resolve that package from the app install directory; (2) check whether the projection link in the profile exists; (3) resolve from the profile directory. Testing only (3) misses this failure, because the failing resolution is anchored on the app install directory (#7542).
  5. Distinguish "same graph, different layout": the same mixed rc.2/rc.3 graph boots when flatly hoisted and fails when dsh itself is the tree root. Layout is decisive, not package integrity (#7542).
  6. Read the lines above the summary, not the summary itself: plugin tree failed to load is only the conclusion; the actual cause lines are above it — and at the time that stderr did not actually have them. That is also why the report suggests adding "skip diagnostics" (#7542).
  7. Do not extrapolate "rc.2 will not boot" into "all old versions will not boot": 0.1.2-rc.1 fails differently (prints a URL then does not respond); attribute environment changes and package changes separately (#7542).
  8. Glance at tag hygiene while you are here: around the same period @deepseek-ai/dsh-web-app's own latest was still stuck at 0.0.1-rc.1. This kind of "tag does not advance" is the same class of problem (scripts/release/families.ts maps every version containing - to next, leaving only stable versions to npm's latest default — and a stable version has never been published), worth watching together (#7436, #7542).

When troubleshooting this kind of problem, use DSH Plugin Hub's custom install panel to confirm which channel you installed from (npm package / GitHub source / DSH command), and check the version number and source in the installed list — pin down "which generation, from where" first, then distinguish install-time absence from boot-time resolution, and you will save a lot of detours.

DSH Plugin Hub · Confirm install

Source: Discussion #7436, Discussion #7542.

FAQ

`npm install -g @deepseek-ai/dsh` reports ETARGET saying a @deepseek-ai package cannot be found — what happened?

Some rc version set **is missing a member package**. When 0.1.5-rc.3 was published, @deepseek-ai/dsh-web-app@0.1.5-rc.3 declared @deepseek-ai/dsh-client-ui-sidebar-documentpreview@^0.1.5-rc.3, but that package's rc.3 was never published (its next still pointed at rc.2 at the time). More importantly, **a prerelease range can only be satisfied by a prerelease with the same major.minor.patch**, so ^0.1.5-rc.3 is neither "fall back to rc.2" nor "move forward to 0.1.6-alpha.x" — no version in the registry can satisfy it, and ETARGET is the correct result, not the resolver being flaky (Discussion #7436).

Why does even `latest` fail to install, not just `@next`?

Because dsh@0.1.5-rc.2 (the latest at the time) declared @deepseek-ai/dsh-web-app: ^0.1.5-rc.2, and that caret range matches both rc.2 and rc.3, so npm picks the highest — **rc.3** — dragging it into the same broken chain. One link breaks, and both the latest and next channels go down together (Discussion #7436).

It installed, but `dsh web` will not boot and reports dsh-sandbox-local cannot be resolved — what now?

The latest at the time was 0.1.5-rc.2, and its 63 sibling dependencies are all ^0.1.5-rc.2, which npm resolves to the brand-new **rc.3** — giving a "root rc.2, siblings rc.3" mixed-generation tree. In such a tree @deepseek-ai/dsh-sandbox-local gets nested two levels down, and at boot time one resolution is anchored on the **app install directory**, hence MODULE_NOT_FOUND. The easiest remedy is to **pin by generation rather than install by tag**: npm i -g @deepseek-ai/dsh@0.1.5-rc.3 (or @next), making the whole tree one generation (Discussion #7542).

Some people say you need pnpm to boot — is that true?

pnpm is not a **boot** prerequisite. There is no pnpm spawn on the boot path: only dsh plugin … (which forwards its arguments to pnpm) reports pnpm was not found; initProfile merely **writes** the profile's pnpm-workspace.yaml. In a controlled experiment, rc.3 booted fine on a clean runner without pnpm. However, **plugin management** does need pnpm, so installing a global pnpm is still worthwhile (Discussion #7542).

Is it fixed now?

Both spots are already live. The registry can now resolve @deepseek-ai/dsh-client-ui-sidebar-documentpreview@^0.1.5-rc.3 (previously 404 No match found), and latest has moved from 0.1.5-rc.2 to 0.1.5-rc.3 — fixing "cannot install" and "installs but will not boot" together. If your machine is still pinned at rc.2, just reinstall (Discussion #7436).

Related Terms

prerelease range matching (same major.minor.patch)
A semver rule: a comparator with a prerelease (such as `^0.1.5-rc.3`) is satisfied only by a prerelease with the **same `major.minor.patch`**. So `^0.1.5-rc.3` accepts `0.1.5-rc.3` and `0.1.5-rc.4` but **not** `0.1.6-alpha.2` or `0.1.7-alpha.1`. This is exactly why "missing rc.3 means no way back and no way forward", and it turns this kind of release accident from "partial downgrade" into "hard failure".— https://github.com/deepseek-ai/deepseek-harness/discussions/7436
mixed-generation dependency tree
A dependency tree formed when the root package is one generation (for example `dsh@0.1.5-rc.2`) while its sibling dependencies float to the next generation (rc.3) because of caret ranges. Because `^0.1.5-rc.2`'s prerelease tuple is the same as rc.2's, the higher rc.3 is selected. In such a tree npm's hoist layout differs from a "whole tree one generation" case, and a package can end up nested where the loader cannot see it.— https://github.com/deepseek-ai/deepseek-harness/discussions/7542
dist-tag drift (latest does not advance automatically)
`scripts/release/families.ts` maps any version containing `-` to `next` (`alpha`/`canary` each get their own channel), leaving only stable versions to npm's `latest` default. Because a stable `@deepseek-ai/dsh` has never been published, `latest` stays on whatever prerelease it was pointed at originally — **no release automatically advances it**, and an operator must manually change the registry state.— https://github.com/deepseek-ai/deepseek-harness/discussions/7542
silent skipping (the closure walk's deleted dependencies)
When the bootstrapper walks the installed dependency closure, for a dependency that is "declared but not found at any anchor" it does `continue` (`packages/boot/app-boot/src/profile.ts:494-496`), neither erroring nor naming it. So when a package is missing from the install, the Loader reports only **that entry** as unresolvable, without saying which package or which manifest declared it. Printing "what was skipped" turns this kind of error from "incomprehensible" into "locatable".— https://github.com/deepseek-ai/deepseek-harness/discussions/7542

Sources