You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If any package under a profile's node_modules brings its own copy of @deepseek-ai/dsh-tools, every tool call in that profile fails:
UNKNOWN: Cannot read properties of undefined (reading 'prepare')
The scheduler handle is keyed by a module-local Symbol(), so two copies of dsh-tools yield two non-equal keys and the lookup returns undefined. Nothing fails at load time, the message names neither the tool nor the duplicated package, and it also fires from orphaned packages the profile no longer references.
Impact
Every tool call in the affected profile throws — the agent loop is unusable.
The session is left corrupted: the turn appends tool_calls but never the matching results, so the next request fails upstream with An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The conversation has to be abandoned.
Nothing in the error hints at dependency duplication, so the natural first guess is that the harness or the model provider broke. This took a multi-hour bisect to pin down.
It is exposed as an instance property of the ToolRuntime service (dsh-tools/lib/index.js:2564; declared in dsh-tool-cordis as readonly [TOOL_RUNTIME_SCHEDULER]: ToolRuntimeScheduler) and read back by the agent loop:
Since Symbol() (not Symbol.for()) is identity-scoped per module instance, writer and reader must resolve to the same dsh-tools file. A profile-local copy breaks that.
Resolution on my machine — three distinct instances:
Empty dependencies, and the offending plugin was not in bundles — only a leftover node_modules/@deepseek-ai/ tree from an earlier install. Tool calls still failed on every turn.
Moving that orphaned directory aside fixed it immediately, with no other change:
mv ~/.dsh/profiles/headless/node_modules/@deepseek-ai /tmp/backup/
# the same prompt now answers normally instead of throwing
So uninstalling the plugin isn't enough — a stale node_modules keeps the profile broken, with no configuration anywhere hinting why.
Suggestions
Roughly in order of value:
Fail loud at load time. When composing a profile, realpath every resolved @deepseek-ai/* package and throw if one resolves to more than a single path. This matches the stance the codebase already takes elsewhere — dsh-credentials-local rejects rather than skips, reasoning that "a silently ignored entry reads as 'the credential I stored has no effect'". A duplicated runtime package is the same class of failure, with a much wider blast radius.
Make the key instance-independent: Symbol.for("@deepseek-ai/dsh-tools.scheduler"). Cheap, and downgrades a hard failure to a merely-redundant install. Best combined with (1) so duplication still gets reported.
State the contract for plugin authors.dsh-context gets it right — dependencies: {}, with every @deepseek-ai/* package plus react/zod under peerDependencies — but I couldn't find this written down anywhere, so it reads as a style choice rather than a hard requirement. A line in the plugin docs, plus a warning from dsh plugin add when a plugin declares a @deepseek-ai/* package as a non-peer dependency, would prevent the entire class.
I know external PRs aren't being accepted right now — happy to provide any further detail or to verify a fix against my setup if that's useful.
Environment
@deepseek-ai/dsh
0.1.1-rc.2
Node
v24.15.0
OS
macOS (Darwin 25.5.0, Apple Silicon)
Install
global npm; profiles at ~/.dsh/profiles/{web,headless}
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
If any package under a profile's
node_modulesbrings its own copy of@deepseek-ai/dsh-tools, every tool call in that profile fails:The scheduler handle is keyed by a module-local
Symbol(), so two copies ofdsh-toolsyield two non-equal keys and the lookup returnsundefined. Nothing fails at load time, the message names neither the tool nor the duplicated package, and it also fires from orphaned packages the profile no longer references.Impact
tool_callsbut never the matching results, so the next request fails upstream withAn assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The conversation has to be abandoned.Root cause
TOOL_RUNTIME_SCHEDULERis a module-local symbol:It is exposed as an instance property of the
ToolRuntimeservice (dsh-tools/lib/index.js:2564; declared indsh-tool-cordisasreadonly [TOOL_RUNTIME_SCHEDULER]: ToolRuntimeScheduler) and read back by the agent loop:Since
Symbol()(notSymbol.for()) is identity-scoped per module instance, writer and reader must resolve to the samedsh-toolsfile. A profile-local copy breaks that.Resolution on my machine — three distinct instances:
I did not instrument which instance ends up owning
ctx.tools; the duplication plus theSymbol()identity rule is enough to explain theundefined.Reproduction
{ "name": "my-plugin", "dependencies": { "@deepseek-ai/dsh-tools": "^0.1.1-rc.2" } }dsh plugin --profile <name> add my-plugin— a copy lands in<profile>/node_modules/@deepseek-ai/dsh-tools.Cannot read properties of undefined (reading 'prepare').The part that makes it much worse
The duplicate does not need to be referenced by the profile. My
headlessprofile had:{ "dependencies": {}, "dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-headless"] } } }Empty dependencies, and the offending plugin was not in
bundles— only a leftovernode_modules/@deepseek-ai/tree from an earlier install. Tool calls still failed on every turn.Moving that orphaned directory aside fixed it immediately, with no other change:
So uninstalling the plugin isn't enough — a stale
node_moduleskeeps the profile broken, with no configuration anywhere hinting why.Suggestions
Roughly in order of value:
realpathevery resolved@deepseek-ai/*package and throw if one resolves to more than a single path. This matches the stance the codebase already takes elsewhere —dsh-credentials-localrejects rather than skips, reasoning that "a silently ignored entry reads as 'the credential I stored has no effect'". A duplicated runtime package is the same class of failure, with a much wider blast radius.Symbol.for("@deepseek-ai/dsh-tools.scheduler"). Cheap, and downgrades a hard failure to a merely-redundant install. Best combined with (1) so duplication still gets reported.dsh-contextgets it right —dependencies: {}, with every@deepseek-ai/*package plusreact/zodunderpeerDependencies— but I couldn't find this written down anywhere, so it reads as a style choice rather than a hard requirement. A line in the plugin docs, plus a warning fromdsh plugin addwhen a plugin declares a@deepseek-ai/*package as a non-peer dependency, would prevent the entire class.I know external PRs aren't being accepted right now — happy to provide any further detail or to verify a fix against my setup if that's useful.
Environment
@deepseek-ai/dsh~/.dsh/profiles/{web,headless}All reactions