dshkit

Fix: a dsh plugin installs but never loads

Why an installed Cordis plugin does not appear in your agent: resolution order, per-profile node_modules, a missing patch row, or a higher patch layer overriding it. Diagnose with --dump-config.

Updated 2026-08-133 min
Short answer

Installing a plugin puts code in the profile's node_modules; it does not add a config row. A plugin loads only when a bundle or a patch layer inserts a row that mounts it. Run dsh --profile <name> --dump-config: if the row is absent you never added it, and if it is present but wrong, a higher patch layer overrode it.

Almost every "my plugin does not work" report in the harness is one of four things, and they are distinguishable in about a minute. The root cause is usually a single wrong assumption:

Step 1 — is it in the composition at all?

dsh --profile web --dump-config

This prints every row that patches can target, after all layers have applied. Search it for your package name.

Not there → the package may be installed, but nothing mounts it. Go to cause A.

There, but configured differently than you wrote → a higher layer overrode you. Go to cause C.

There, and correct, but the capability is missing at runtime → go to cause D.

Cause A — installed, never mounted

The most common case. You ran:

dsh plugin --profile web add some-cordis-plugin

...which installed the package and stopped. You still need a row that mounts it, in the profile's patch file at $DSH_HOME/profiles/web/cordis.patch.yml. A patch targets a row by identifier and either replaces its whole config or inserts new rows — inserting is what you want here.

Then verify with --dump-config again. Do not skip the verification: this is exactly the class of change that silently does nothing.

Cause B — installed into a different profile

Plugins install into the profile's own node_modules. That isolation is deliberate — it lets two profiles hold incompatible plugin sets — but it means:

dsh plugin --profile web add thing     # installs into the web profile
dsh --profile headless                 # cannot see it

Check that the profile you installed into is the profile you are booting. If you want it in both, install it twice.

Also relevant: bundles resolve from the dsh installation first, then from the profile's node_modules. If a name exists in both places, the installation's copy wins. A local package intended to shadow a shipped one will not, by name alone — override the shipped row with a patch instead.

Cause C — a higher layer is overriding you

The layer order, lowest priority first:

  1. each bundle the profile stacks, in sequence
  2. $DSH_HOME/profiles/<name>/cordis.patch.yml
  3. $DSH_HOME/cordis.patch.yml
  4. --patch overlay at runtime

Later wins. The single most common version of this bug: someone sets something in the home-level patch file months ago, forgets, then edits the profile-level file and sees nothing change.

# what a clean profile composes to
dsh --profile web --dump-default-config > /tmp/base.txt
 
# what you actually boot
dsh --profile web --dump-config > /tmp/mine.txt
 
diff /tmp/base.txt /tmp/mine.txt

That diff is precisely the set of changes your layers make. If your intended change is not in it, you did not make it. If it is in it but wrong, something later rewrote it.

Cause D — mounted, but the capability has no provider

Some tools need something behind them. The lsp tool, for example, requires a registered LSP provider — with none, the tool mounts but has nothing to query.

The harness models capabilities as seams: a Service Definition owning ctx.<key>, one or more Service Providers, and Consumers that inject the service. A consumer with no provider is a coherent composition that does nothing useful, and nothing will error at boot to tell you so.

If a plugin appears in --dump-config and still does not work, ask what service it consumes and whether anything provides it.

Install-time failures

If dsh plugin add itself fails, remember it is pnpm underneath, and the error is a pnpm error:

  • ERR_PNPM_FETCH_404 — the package name is wrong or unpublished.
  • Peer dependency warnings — a plugin built against a different core version. On a v0.1 preview with expected compatibility-breaking changes, take these seriously rather than forcing past them.
  • Network or registry errors — your registry config, not the harness.

The full pnpm vocabulary works, so a version specifier pins a plugin to a version that matches your harness:

dsh plugin --profile web add [email protected]

A checklist

  1. --dump-config — is the row present?
  2. Did you install into the profile you are booting?
  3. diff against --dump-default-config — is your change actually in the delta?
  4. Is there a home-level patch layer you have forgotten about?
  5. Does the plugin consume a service that nothing provides?

Frequently asked

Does dsh plugin add enable the plugin?

No. It forwards to pnpm and installs the package into that profile's node_modules. Mounting it is a separate step — a config row, inserted by a bundle or by one of your patch layers.

I installed into the wrong profile. How do I tell?

Plugins are per-profile. Check that the --profile you installed with matches the one you are booting; a plugin in the web profile's node_modules is invisible to the headless profile.

Why does my plugin load but with the wrong settings?

A later patch layer replaced its row's config. Layers apply as bundles, then the profile's cordis.patch.yml, then the home-level one, then --patch — and later wins.

Keep reading