DSH plugin: silent exit code 0 on Node 24.0/24.1

TroubleshootingPublished 2026-10-03Author: DeepSeek Plugin Market
DeepSeek HarnessDSHNode.jsimport.meta.mainsilent exitenginesversion compatibility
Node.js v24.0.x / v24.1.0: running dsh prints nothing, writes no log, listens on no port, and exits with code 0 — import.meta.main only landed in Node 24.2.0.

Running dsh web on Node.js v24.0.x / v24.1.0: it prints nothing, the log file is empty, it never listens on port 3080, and the process then exits with code 0 — looking like a "clean exit" while the CLI body was never executed at all. The root cause is in lib/bin.js's entry guard: if (import.meta.main) await runCli();, and import.meta.main was only introduced in Node v24.2.0 (v22.18.0 on the 22.x line); on v24.1.0 the property is undefined, so runCli() is never called and the process exits cleanly after loading its modules (#6584). Worse, there is no guardrail at all: the root package.json's engines range ^22.19.0 || >=24.0.0 happens to include the problematic 24.0 / 24.1, apps/cli/package.json has no engines whatsoever, and npm itself only warns on engines rather than blocking. This article proceeds as "triage first → root cause and usable boundary → three fixes → the same family and troubleshooting notes".

Triage first: exit code 0, no output, no log — measure the Node version first

For this class of "nothing happened" failure, the first step is always to confirm which Node you are actually running (and whether nvm actually switched) — because there is no other clue in the symptom.

CriterionSilent exit from a version breakpointA genuine boot failure (port in use / native module / proxy)
Any output?none at all (both stdout and stderr empty)usually an error, a stack, or at least one line of guidance
Exit code0usually non-zero
Log fileemptymay have content
Listening on a port?never listensmay already be listening but occupied / will not come up
After switching Node versionsrecovers immediatelysymptom unchanged
Key criterionimport.meta.main is undefinedimport.meta.main is fine

Two lines to identify it first

bash
# 1. Which Node you are actually running (after an nvm switch, always reopen the terminal or confirm with an absolute path)
node -v

# 2. Ask the runtime directly: does this API exist?
node -e "console.log(import.meta.main)"
# Node 24.1.0 → undefined   ← this is the issue
# Node 24.2.0+ → true

If step 1 lands on 24.0.x / 24.1.0 and step 2 is undefined, you have essentially pinned it. The value of this criterion is that it rules out "am I being bitten by WDAC / a native module / a proxy" in one stroke — the reporter checked exactly those first and spent a lot of time before finding it was a Node minor-version difference. Another user also mentioned that Node on some open-source mirror stopped exactly at v24.1.0, leading to three hours of troubleshooting a "perfect flash-exit".

Root cause: import.meta.main is undefined on Node 24.0/24.1

In one sentence: the entry check depends on a Node API introduced relatively recently, while the declared minimum Node version includes minor versions that do not support it — so the guard is always false and the CLI never starts.

The entry code (lib/bin.js):

js
if (import.meta.main) await runCli();

import.meta.main determines "is the current module the entry module", and it was introduced in Node 24.2.0 (22.x line 22.18.0). On v24.1.0 accessing it yields undefined, the if is false → runCli() does not run → the module finishes executing → exit code 0.

The usable boundary verified with real binaries

Reproduced with real Node binaries and the real published CLI (not inferred by reading source):

Nodeimport.meta.mainlib/bin.js --version
24.1.0undefined(no output, exit 0)
24.2.0true0.1.5-rc.1
26.7.0true0.1.5-rc.1

The precise boundary is 24.2.0 and 22.18.0, not a blanket "Node 24" — 24.0.x / 24.1.0 are equally affected. This matters: describing the problem as "Node 24 is incompatible" makes people think they need a major-version downgrade, when in fact they only need to upgrade to ≥ 24.2.

Why engines did not block it

Three reasons compound:

  1. The range itself is written wrong: the root package.json declares "engines": { "node": "^22.19.0 || >=24.0.0" }, and >=24.0.0 explicitly includes 24.0.0 / 24.1.0, which lack import.meta.main — the range declares "supported" while it is in fact not.
  2. The entry package has no engines: apps/cli/package.json has no engines field; apps/cli/src/bin.ts is 66 lines with no runtime Node version check at all.
  3. npm does not block: even if engines existed and were correct, npm only warns on engines and does not prevent installation. So users on 24.1.0 "install successfully" and then silently exit, with no way to connect it to a version mismatch.

One more point on the version-line judgment: the review conclusion is that rc.2 will not fix it — rc.1 / rc.2's entry path is the same master line. This belongs to the "Node versions have no runtime gate" family (#6520 checklist item 7); the linked #4047 (Node 24.0 likewise flash-exits) is in the same family.

Fix one (user side): upgrade Node to ≥ 24.2 or 22.18+

Idea: since the breakpoint is at a minor version, the most direct fix is to raise the runtime past it.

  1. Use nvm / nvm-windows to switch to Node ≥ 24.2 (or ≥ 22.18 on the 22.x line).
  2. Reopen the terminal to confirm the switch took effect: node -v, then node -e "console.log(import.meta.main)" should be true.
  3. Run dsh web again; it should output normally and listen on a port.

If you use some open-source mirror, note that it may only sync up to v24.1.0 — confirm the target version actually exists before upgrading (exactly the pit another reporter fell into).

Another possible bypass (per the reviewer): install globally and run dsh directly, to sidestep the silent-exit behavior under the npx path. Mechanically, though, the missing import.meta.main is a runtime property issue, so upgrading Node is the correct answer; this bypass only works under a specific entry difference.

Fix two (bridge): dsh-node-compat — usable today

Idea: make the CLI run on the old minor versions without changing upstream. lib/bin.js already exports runCli, and runCli() reads process.argv.slice(2); the launcher just imports the same module, calls runCli() itself, and passes the arguments through verbatim.

sh
npx dsh-node-compat web
# or: npm i -g dsh-node-compat && alias dsh=dsh-compat

Its behavior across runtimes (verified with real binaries):

Nodeimport.meta.mainlib/bin.js --versiondsh-compat --version
24.1.0undefined(no output, exit 0)diagnostic + 0.1.5-rc.1
24.2.0true0.1.5-rc.10.1.5-rc.1
26.7.0true0.1.5-rc.10.1.5-rc.1

Two key properties:

  1. On runtimes where the guard works it is completely silent — it adds no noise for normal users;
  2. It never runs twice — when the launcher is the main module, the import.meta.main that bin.js sees is false, so it will not call runCli() a second time itself.

The bridge's npm package and repo: dsh-node-compat (author Robin1987China, a community solution, not an official component; the original run records are in its repo's docs/verification.md).

Fix three (upstream): portable guard + tightened engines + runtime version guard

Idea: decouple the "entry check" from a version dependency, then add two guardrails so same-class problems from now on present as a "loud failure" instead of a "silent exit".

  1. Switch the entry guard to a portable form (a temporary fix, two lines):
js
import { fileURLToPath } from "node:url";
if (process.argv[1] === fileURLToPath(import.meta.url)) {
  await runCli();
}
  1. Tighten and complete engines: change it to "node": "^22.19.0 || >=24.2.0", and make sure it is actually included in the published npm package (the entry package apps/cli/package.json needs it too). Note this only makes it "warn at install time", not a hard block.

  2. Add a runtime version guard at startup: if Node < 22.19.0, or Node 24.x and < 24.2.0, print the explicit version requirement and exit non-zero. This is the most important of the three — it turns a silent failure into a loud failure, and that is exactly the reporter's core request: "a clear error message or a stricter engines field would save users a lot of trouble."

These three align with the direction of #6605's proposal; the reviewer has added #6584 / #6605 to #6520 item 7's "related discussions".

Troubleshooting notes

First principle: when you hit "exit code 0, no output, no log", measure the runtime's capability with node -e "console.log(import.meta.main)" rather than suspecting system policy or the network. This kind of silent failure erases every conventional clue.

  1. node -v alone is not enough to identify it: 24.0.x / 24.1.0 / 24.2.0 are all "Node 24", yet their behavior is completely different. Check by minor version (#6584).
  2. After switching versions with nvm, always reopen the terminal and confirm: a switch that did not take effect will have you repeatedly verifying on the wrong version.
  3. Do not start by checking WDAC / native modules / proxies: the reporter checked those first and only at the end found it was a version difference. Doing the "two-line identification" first saves a lot of time (#6584).
  4. Watch for the version ceiling on mirrors: one open-source mirror's Node stopped exactly at v24.1.0, a high-incidence source of this kind of "install then flash-exit"; confirm the target version is genuinely downloadable before upgrading.
  5. engines is not a guardrail: npm only warns by default and does not block installation; and in this case the engines range itself included the broken versions. The genuinely reliable backstop is a runtime version guard (#6584).
  6. Do not count on rc.2 fixing it along the way: rc.1 / rc.2's entry path is the same master line, apps/cli/src/bin.ts has no runtime version check and apps/cli/package.json has no engines (#6584).
  7. This is a "family", not an isolated case: #4047 (Node 24.0 flash-exit) and #6520 item 7 (Node versions have no runtime gate) are in the same family; hit one and you can troubleshoot another by the same pattern (#6584).

When troubleshooting this kind of problem, use DSH Plugin Hub's system logs page to export diagnostics and confirm the version state of DSH and its plugins — that rules out the "incompatible plugin/component" layer first, before you come back to the Node runtime layer. In this case the root cause is precisely not in any plugin, but at the boundary of runtime capability.

DSH Plugin Hub · System logs

Source: Discussion #6584.

FAQ

Running `dsh web` outputs nothing, never listens on a port, and the exit code is 0 — did it crash?

It did not crash — **the CLI body was never executed**. The entry guard reads if (import.meta.main) await runCli();, and import.meta.main was only introduced in Node **24.2.0** (**22.18.0** on the 22.x line); on Node 24.0.x / 24.1.0 it is undefined, so runCli() is never called and the process exits cleanly after loading its modules. Hence **no error, no log, exit code 0** (Discussion #6584).

Why did npm not block it at install time? Does the `engines` field not work?

Two reasons compound. First, the root package.json declares the range ^22.19.0 || >=24.0.0, which **itself includes 24.0.0 / 24.1.0, the versions missing import.meta.main**. Second, apps/cli/package.json **has no engines**, and engines does not appear to be carried in the actually published package; and even if it were, **npm only warns on engines, it does not block**. So Node 24.1 "installs successfully" and then silently exits (Discussion #6584).

Is this "Node 24 is incompatible"? Which version should I downgrade to?

**It is not a blanket Node 24 problem, it is a minor-version breakpoint.** The usable boundary verified with real Node binaries is **24.2.0** and **22.18.0** — 24.0.x / 24.1.0 are affected, and 24.2.0 onward is fine. So just upgrade to **Node ≥ 24.2** (or 22.18+); no major-version downgrade is needed. The linked #4047 (Node 24.0 likewise flash-exits) belongs to the same family (Discussion #6584).

I cannot upgrade Node right now — is there something usable today?

You can use the community bridge dsh-node-compat for now: it imports the same lib/bin.js (which already exports runCli), calls runCli() itself, and passes the arguments through verbatim — npx dsh-node-compat web, or npm i -g dsh-node-compat && alias dsh=dsh-compat. On runtimes where the guard works it is completely silent; on runtimes where the guard fails it prints a diagnostic first. It will not run twice, because when the launcher is the main module, the import.meta.main that bin.js sees is false (Discussion #6584).

Will this be fixed in rc.2?

No. The review conclusion is that rc.1 / rc.2's entry path is **the same master line**: apps/cli/src/bin.ts is 66 lines with **no runtime Node version check at all**, and apps/cli/package.json has no engines either. This belongs to the "Node versions have no runtime gate" family (#6520 checklist item 7) and needs an upstream entry guard to be truly fixed (Discussion #6584).

Related Terms

import.meta.main
A Node.js ESM meta-property for determining "is the current module the entry module". It was only introduced in **Node 24.2.0** (**22.18.0** on the 22.x line); before that, accessing it yields `undefined`. Using it as a guard (`if (import.meta.main) await runCli()`) silently skips "the body that should have run" on older minor versions — the direct cause of the silent exit in this article.— https://github.com/deepseek-ai/deepseek-harness/discussions/6584
portable entry detection
An equivalent form that does not depend on `import.meta.main`: `import { fileURLToPath } from "node:url"; if (process.argv[1] === fileURLToPath(import.meta.url)) { await runCli(); }`. It holds on every supported runtime, so decoupling the "entry check" from a version dependency is one of the most direct temporary fixes for this accident.— https://github.com/deepseek-ai/deepseek-harness/discussions/6584
engines field (npm)
The `package.json` field declaring the required runtime environment, for example `"engines": { "node": "^22.19.0 || >=24.2.0" }`. Two things are commonly misunderstood: first, **npm only warns on engines by default and does not prevent installation**; second, the range itself must be written correctly — including unsupported minor versions is more misleading than omitting it. The genuinely reliable backstop is a **runtime version guard**: print the explicit requirement and exit non-zero when unmet.— https://github.com/deepseek-ai/deepseek-harness/discussions/6584
silent failure
A process that produces no output, no non-zero exit code, and even an illusion that "everything is fine". It is harder to troubleshoot than a crash, because every conventional clue (error stack, log, exit code) is gone — in this case the reporter checked WDAC policy, native module builds, and proxy settings before finally finding it was a Node minor-version difference. **Turning a silent failure into a loud failure is itself a fix.**— https://github.com/deepseek-ai/deepseek-harness/discussions/6584

Sources