Fix "Cannot read properties of undefined (reading 'prepare')" in DSH

TroubleshootingPublished 2026-08-27Author: DSH Plugin Hub
DeepSeek HarnessDSH pluginreading prepareduplicate core packagetool call error
DSH crashes reading 'prepare'? Duplicate @deepseek-ai/dsh-tools in profile breaks tool dispatch (Symbol mismatch). One command finds it; clean it up.

DeepSeek Harness throwing Cannot read properties of undefined (reading 'prepare') on every tool call usually means a second copy of @deepseek-ai/dsh-tools sits inside the profile — the tool scheduler is keyed by a module-local Symbol(), the two copies never share the key, and the lookup comes back undefined. One find command locates it, cleanup restores the profile; but sessions that already carry dangling tool_calls cannot be recovered and must be abandoned.

What the DeepSeek Harness "reading 'prepare'" error looks like

The error is always UNKNOWN: Cannot read properties of undefined (reading 'prepare'), it fires on every tool call, and nothing fails at load time. A user hit it in practice (discussion thread) with these symptoms:

  1. The profile starts normally, but any tool call throws Cannot read properties of undefined (reading 'prepare');
  2. The error names neither the failing tool nor the duplicated package — the natural first guess is the framework or the model provider;
  3. Worse, there is a secondary injury: the failed turn writes tool_calls into the log without results, and the next request is rejected by the model provider (An assistant message with 'tool_calls' must be followed by tool messages...), which makes the whole session unrecoverable.

This error is hard to debug because the problem hides in dependency resolution, not in runtime logic: TOOL_RUNTIME_SCHEDULER is a module-local Symbol() (defined in dsh-tools), and Symbol() identity is isolated per module instance — the reader and the writer must resolve to the same dsh-tools file. As soon as the profile holds a second copy, the two sides no longer share the key (source).

Why a DSH plugin can install two copies of a core package

A duplicated core package has three sources: a plugin declaring core packages as direct dependencies, a packed tarball dragging in transitive copies, and stale node_modules from an earlier install. One by one:

  1. A plugin declares @deepseek-ai/* as direct dependencies: the correct place is peerDependencies, so the dependency tree resolves exactly one copy; as dependencies it installs a second copy into the profile;
  2. Installing from a packed tarball: installing the DSH binary as a packed tarball most easily drags in duplicated transitive copies, especially when a package declares an over-wide peer range;
  3. Stale node_modules: the most hidden one — the reporter's headless profile had an empty dependency list and the offending plugin was not in bundles either, yet an old node_modules/@deepseek-ai/ tree was still there and tool calls failed every round. Moving the leftover directory away restored it immediately (mv ~/.dsh/profiles/headless/node_modules/@deepseek-ai /tmp/backup/).

So "uninstalling the plugin" is not enough — as long as a copy still lies in node_modules, the profile stays broken, and no configuration tells you why.

How to find the duplicate core package: one find command

The check is a single find: the profile's node_modules must not resolve any @deepseek-ai core package — the core comes from the CLI's own dependency tree, the profile should only hold plugins. Step by step:

  1. Locate the profile directory and confirm the profile name:
bash
ls ~/.dsh/profiles/
  1. Check for a dsh-agent copy — it should print nothing:
bash
find ~/.dsh/profiles/<name>/node_modules -maxdepth 4 -path '*/@deepseek-ai/dsh-agent' -print
  1. Check dsh-tools the same way — again nothing:
bash
find ~/.dsh/profiles/<name>/node_modules -maxdepth 4 -path '*/@deepseek-ai/dsh-tools' -print
  1. Output means two copies are installed: note the found directories, delete them or move them to a backup folder:
bash
mv ~/.dsh/profiles/<name>/node_modules/@deepseek-ai /tmp/dsh-backup/
  1. Restart dsh (Ctrl+C, then dsh web again), and in a new session ask the Agent to call a tool — no more reading 'prepare' means it is fixed.

Verified by the reporter: moving the whole node_modules/@deepseek-ai directory (instead of package by package) cleans it up in one go; rerun the same command with dsh-agent / dsh-tools to confirm nothing remains.

Fix and prevention: cleaning duplicates and DSH plugin dependency conventions

The fix has two layers: upstream should switch the key to a cross-copy Symbol.for() (with a protocol version guard), and plugin authors should put core packages in peerDependencies. The community confirmed the root cause and suggested this direction (source):

  1. Switch the key to Symbol.for('@deepseek-ai/dsh-tools.scheduler'): both copies then compute the same key, and a hard failure degrades into "merely a redundant install";
  2. Add a protocol version guard: Symbol.for alone only fixes access — copies of different versions share the same key, and if the scheduler shape changes across versions, a loud crash turns into a silent mismatch. The registrar should stamp a version, and the reader should validate it and report both versions;
  3. Fail loudly at load time: when composing a profile, run a realpath check on each @deepseek-ai/* package and error out if one package resolves to multiple paths, instead of waiting for a runtime explosion;
  4. Plugin author contract: dsh-context is the correct example (dependencies: {}, all @deepseek-ai/* plus react/zod in peerDependencies). Official docs should pin this down, and dsh plugin add should warn when a plugin declares a core package as a non-peer dependency.

Until the upstream fix lands, the find-and-clean in this article restores the profile. This debugging story is also a reminder that the trustworthiness of the plugin source matters — human-verified plugins in the catalog produce far fewer of these packaging problems. If you would rather not wrestle with the dependency tree by hand, the desktop app's built-in DSH Plugin Hub (dsh-plugin.org) manages installed plugins centrally — what is installed is visible at a glance, and uninstall-and-cleanup takes one click.

Notes

  1. The error appears when a second core package exists — run find first before suspecting the model or the framework, and do not burn time in the wrong direction.
  2. A new session is mandatory after cleanup: the old failed session already carries tool_calls without results, and the model provider keeps rejecting it.
  3. Check every profile when you have several; residue in any one of them kills tool calls for that profile.
  4. Similar plugin errors are collected in the DeepSeek Harness plugin error collection: DSH plugin not loading, Web UI issues, and session cache repair.

Sources: Discussion #4640, packages/core/tools (dsh-tools)

FAQ

Why does DeepSeek Harness throw Cannot read properties of undefined (reading 'prepare') on every tool call?

A second copy of @deepseek-ai/dsh-tools exists in the profile's node_modules. The tool scheduler uses a module-local Symbol() as its key, the two copies never share the key, and the agent loop resolves the scheduler to undefined when it builds a service from the other copy (source).

Why would a @deepseek-ai core package end up duplicated in the profile's node_modules?

Most commonly a plugin declares @deepseek-ai/* as direct dependencies (peerDependencies is correct); secondly, installing the CLI from a packed tarball drags in transitive copies; thirdly, stale node_modules left by an earlier install keeps breaking tool calls even when the profile no longer references them.

How do I locate the duplicated @deepseek-ai/dsh-tools core package with find?

Use find against the profile's node_modules: find <profile>/node_modules -maxdepth 4 -path '*/@deepseek-ai/dsh-agent' -print should print nothing; repeat with dsh-tools in place of dsh-agent. Any output means a second copy is installed; move it away and restart dsh web to recover.

Can I keep using a session after tool calls failed with reading 'prepare' this way?

No. The failed turn leaves tool_calls without matching results, and the next request is rejected by the model provider with "An assistant message with 'tool_calls' must be followed by tool messages". The session must be abandoned and a new one started.

How do I recover after the duplicate core package breaks tool calls? What are the cleanup and restart steps?

Remove the duplicate. Delete or move away the leftover node_modules/@deepseek-ai copy under the profile (for example mv ~/.dsh/profiles/<name>/node_modules/@deepseek-ai /tmp/dsh-backup/), restart dsh (Ctrl+C, then dsh web again), and tool calls work again in new sessions; already corrupted sessions are abandoned.

Sources