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
[Bug] v0.1.6-alpha.1: browser-use runtime declares dsh-scope as a dependency — the profile loads two copies and every session after the first fails
Summary
Since v0.1.6-alpha.1, any profile that mounts the official browser-use provider (@deepseek-ai/dsh-browser-use + @deepseek-ai/dsh-experimental-browser-use-playwright-mcp) can no longer create a second session. The first session works; every session created while it is open fails, with the UI either doing nothing or discarding a saved session row.
The cause is not browser-use. @deepseek-ai/dsh-experimental-browser-use-runtime@0.1.6-alpha.1 declares @deepseek-ai/dsh-scope as an ordinary dependency, so the package manager materializes a second physical copy of dsh-scope inside the profile's own node_modules. dsh-scope identifies a scope with a plain per-module symbol, so with two copies scopeOf(agent.ctx) returns undefined for a genuinely scoped context. Core APIs that need that resolution then either throw or silently fall back to the global layer, and because the failing calls run inside the agent/created hook, session creation aborts.
The same manifest already lists @deepseek-ai/dsh-tools, dsh-agent, dsh-browser-use and cordis as peerDependencies — exactly the treatment dsh-scope and dsh-mcp-client do not get.
Environment
dsh
@deepseek-ai/dsh@0.1.6-alpha.1 (npm alpha)
dsh-scope
0.1.6-alpha.1, two physical copies loaded per process
browser-use packages
@deepseek-ai/dsh-browser-use, @deepseek-ai/dsh-experimental-browser-use-playwright-mcp, @deepseek-ai/dsh-experimental-browser-use-runtime — all 0.1.6-alpha.1
DevTools → Console (with Preserve log) shows the rejected RPC:
{
"type": "server-response",
"rpcId": "…",
"result": {
"ok": false,
"error": {
"code": "gateway/internal",
"message": "failed to create session \"session-…\": Error: tools.restrict() requires a scoped context (agent.ctx): a context-global restriction would mask every agent — deny the tool for the intended agent instead"
}
}
}
UI-level symptoms of this one failure:
Clicking "New session" does nothing
Clicking a saved session makes its row vanish until the page is refreshed
Choosing a workspace folder fails to create a session
A ~330-byte orphan session file is written each time
exactly 3 records, then nothing
The same defect produces a second, differently-worded error when the second Agent goes down the tool-registration path instead:
failed to create session "session-…": Error:
mcp-client(playwright-mcp): initial connection or tool synchronization failed
with the underlying cause visible only in the server log:
mcp-client(playwright-mcp): tool registration failed, no tools registered:
Error: tool "mcp__playwright-mcp__browser_close" is already registered
(for a per-agent variant, register through that agent's `agent.ctx` instead)
at NamedEntries.duplicateError (…/@deepseek-ai/dsh-tools/lib/index.js:2634:59)
at ScopedLayers.<anonymous> (…/@deepseek-ai/dsh-scope/lib/index.js:206:12)
at Proxy.register (…/@deepseek-ai/dsh-tools/lib/index.js:2884:22)
Expected result
Session creation should not depend on whether another session holds the browser. The provider's own contract says so — dsh-experimental-browser-use-runtime/lib/types/mcp.js:39-45:
Await one MCP client during each future Agent's creation. A busy attachment leaves that activation without browser tools; its other turns continue. Calls are serialized per Session.
i.e. the second activation should lose browser tools and keep working, not abort session/create.
Evidence — verified against the npm registry
The published manifests were read directly from the registry.
dsh-tools, dsh-agent, dsh-browser-use and cordis are peers — the host provides them, one copy each. dsh-scope and dsh-mcp-client are ordinary dependencies, so the package manager is obliged to materialize its own copies.
2. @deepseek-ai/dsh-experimental-browser-use-playwright-mcp@0.1.6-alpha.1 depends only on @playwright/mcp@0.0.80 and the runtime; it does not pull dsh-scope directly. The duplication originates in the runtime's manifest.
createScopewritesctx[kScope]; scopeOfreadsctx[kScope]. Symbol() returns a new unique symbol on every evaluation, so the handshake only works when both sides hold the same object — i.e. when dsh-scope exists once in the process. With two copies, scopeOf(agent.ctx) is undefined.
Two core APIs then ask that question and get the wrong answer:
A — the designed-degradation path throws. In dsh-experimental-browser-use-runtime/lib/types/mcp.js, a second activation against an exclusive resource is admitted as blocked, then masked:
dsh-tools/lib/index.js:2894-2895 then throws, and the exception escapes refreshBlockedMasks → the agent/created handler at :129-141 → session creation.
B — tool registration lands in the global layer.dsh-tools.register() is documented as "Register globally or in the calling agent scope" and resolves the layer with the same scopeOf (:2876-2884). Degraded, every Agent's MCP tools are inserted into the global layer, so the second insertion trips NamedEntries.duplicateError (:2634) — the stack quoted under Actual result. Because the provider hardcodes failOnStartupError: true (mcp.js:102), dsh-mcp-client swaps its default registrationFailure: "contain" for "throw" (dsh-mcp-client/lib/index.js:489-493, rethrown at :155-157) and wraps it as initial connection or tool synchronization failed (:832), which is what makes an otherwise contained conflict fatal.
Why mode: launch hides it
The provider makes the browser resource exclusive only in attach mode (dsh-experimental-browser-use-playwright-mcp/lib/index.js:40):
exclusive: config.mode==="attach",
so under mode: launch — the example in the provider README — available() short-circuits to true for every Agent (dsh-experimental-browser-use-runtime/lib/types/index.js:45-49), status is never 'blocked', and line :65 is unreachable. The defect needs attach mode or two concurrent Agents to surface; with a single session nothing looks wrong. Ordinary MCP servers are also unaffected because they keep the default failOnStartupError: false, which contains the same conflict.
Workaround (confirmed working)
Replace the profile-local real copies of modules that are also provided by the host with directory junctions pointing at the host copy, so each module has exactly one physical file per process:
# quit dsh completely first — a running node.exe keeps these directories locked$p="$env:USERPROFILE\.dsh\profiles\repro\node_modules\@deepseek-ai"Move-Item"$p\dsh-scope""$p\dsh-scope.bak"New-Item-ItemType Junction -Path "$p\dsh-scope"`-Target "$(npm root -g)\@deepseek-ai\dsh\node_modules\@deepseek-ai\dsh-scope"
Applied to all five duplicated modules (dsh-scope, dsh-mcp-client, cosmokit, dsh-util-values, schemastery); the three profile-only browser-use packages are correctly left alone, as the host has no copy to point at. Only dsh-scope is strictly necessary — the other four do not export identity tokens. Session creation recovers immediately.
Two caveats: it must be re-applied after any pnpm install / dsh plugin add, and it should touch only <profile>\node_modules\@deepseek-ai\* — do not wipe $DSH_HOME/profiles/node_modules, since the heal step rebuilds only the install closure and would drop third-party scopes.
Suggested fixes (any of the three)
(a) Move @deepseek-ai/dsh-scope and @deepseek-ai/dsh-mcp-client from dependencies to peerDependencies in @deepseek-ai/dsh-experimental-browser-use-runtime and re-publish — the identical treatment dsh-tools, dsh-agent and dsh-browser-use already receive in that same manifest. This removes the duplication at its source for every profile.
(b) Make the scope tag cross-instance in core: packages/core/scope/src/index.ts:18 → const kScope = Symbol.for('dsh.scope'). This is layout-independent and is the level the fix belongs at; it is also this repo's existing idiom (Schemastery uses Symbol.for('schemastery')). Note it covers the Symbol facet only — dsh-scope also keeps carrier state in module-local collections, so (a) and (b) are complementary, not alternatives.
(c) Contract the blast radius: give refreshBlockedMasks a catch so the documented blocked degradation cannot abort agent/created; do not let a per-Session provider mount turn session/create into a 500; and include cause in the wrapped MCP error, which currently sends diagnosis toward "connection" when the failure is registration.
Related reports
#3148 — same root cause, same one-line fix, with a branch to cherry-pick (kongminOS/deepseek-harness:fix/dsh-scope-symbol-for).
#2149 — same class of defect via dsh-scope's module-local WeakMap carrier registry (isScopeCarrier() false for every dispatch).
#6722 and #6755 — the same browser-use symptom under mode: launch, with the duplicate-registration stack.
#3984 → #4025 — the design-level note that activeServerNames is keyed process-wide while the tool registry is per-agent, i.e. the ctx.root fallback this report explains the precondition of.
#6459 — the shared profiles/node_modules fallback tree is rewritten per install anchor; do not delete it wholesale.
Screenshots of the install layout, the two-copies check and the workaround's verification output are attached below.
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.
Uh oh!
There was an error while loading. Please reload this page.
[Bug] v0.1.6-alpha.1: browser-use runtime declares
dsh-scopeas a dependency — the profile loads two copies and every session after the first failsSummary
Since v0.1.6-alpha.1, any profile that mounts the official browser-use provider (
@deepseek-ai/dsh-browser-use+@deepseek-ai/dsh-experimental-browser-use-playwright-mcp) can no longer create a second session. The first session works; every session created while it is open fails, with the UI either doing nothing or discarding a saved session row.The cause is not browser-use.
@deepseek-ai/dsh-experimental-browser-use-runtime@0.1.6-alpha.1declares@deepseek-ai/dsh-scopeas an ordinary dependency, so the package manager materializes a second physical copy ofdsh-scopeinside the profile's ownnode_modules.dsh-scopeidentifies a scope with a plain per-module symbol, so with two copiesscopeOf(agent.ctx)returnsundefinedfor a genuinely scoped context. Core APIs that need that resolution then either throw or silently fall back to the global layer, and because the failing calls run inside theagent/createdhook, session creation aborts.The same manifest already lists
@deepseek-ai/dsh-tools,dsh-agent,dsh-browser-useandcordisas peerDependencies — exactly the treatmentdsh-scopeanddsh-mcp-clientdo not get.Environment
@deepseek-ai/dsh@0.1.6-alpha.1(npmalpha)0.1.6-alpha.1, two physical copies loaded per process@deepseek-ai/dsh-browser-use,@deepseek-ai/dsh-experimental-browser-use-playwright-mcp,@deepseek-ai/dsh-experimental-browser-use-runtime— all0.1.6-alpha.1dsh --profile repro --from-default-profile web)Steps to reproduce
Install
@deepseek-ai/dsh@0.1.6-alpha.1and create an empty profile:Add the official browser-use plugins (the documented flow — the CLI appends them to
dsh.profile.bundles):Because
mode: launchmasks the failure (see below), mount the provider in attach mode instead.%USERPROFILE%\.dsh\profiles\repro\cordis.patch.yml:Start a Chrome with a live CDP endpoint and start the UI:
Create session #1, send
hi. It works, and browser tools are available.Leave session #1 open. Create session #2, send
hi. It fails.Actual result
DevTools → Console (with Preserve log) shows the rejected RPC:
{ "type": "server-response", "rpcId": "…", "result": { "ok": false, "error": { "code": "gateway/internal", "message": "failed to create session \"session-…\": Error: tools.restrict() requires a scoped context (agent.ctx): a context-global restriction would mask every agent — deny the tool for the intended agent instead" } } }UI-level symptoms of this one failure:
The same defect produces a second, differently-worded error when the second Agent goes down the tool-registration path instead:
with the underlying cause visible only in the server log:
Expected result
Session creation should not depend on whether another session holds the browser. The provider's own contract says so —
dsh-experimental-browser-use-runtime/lib/types/mcp.js:39-45:i.e. the second activation should lose browser tools and keep working, not abort
session/create.Evidence — verified against the npm registry
The published manifests were read directly from the registry.
1.
@deepseek-ai/dsh-experimental-browser-use-runtime@0.1.6-alpha.1—https://registry.npmjs.org/@deepseek-ai/dsh-experimental-browser-use-runtime/0.1.6-alpha.1dsh-tools,dsh-agent,dsh-browser-useandcordisare peers — the host provides them, one copy each.dsh-scopeanddsh-mcp-clientare ordinary dependencies, so the package manager is obliged to materialize its own copies.2.
@deepseek-ai/dsh-experimental-browser-use-playwright-mcp@0.1.6-alpha.1depends only on@playwright/mcp@0.0.80and the runtime; it does not pulldsh-scopedirectly. The duplication originates in the runtime's manifest.3. Resulting install layout —
<profile>\node_modules\@deepseek-ai\:None of these is a link, while the host continues to resolve its own copies from the global install:
Root cause
packages/core/scope/src/index.ts(read from a pristine checkout —git status --porcelainempty,HEAD 141eb6fef8):createScopewritesctx[kScope];scopeOfreadsctx[kScope].Symbol()returns a new unique symbol on every evaluation, so the handshake only works when both sides hold the same object — i.e. whendsh-scopeexists once in the process. With two copies,scopeOf(agent.ctx)isundefined.Two core APIs then ask that question and get the wrong answer:
A — the designed-degradation path throws. In
dsh-experimental-browser-use-runtime/lib/types/mcp.js, a second activation against an exclusive resource is admitted asblocked, then masked:dsh-tools/lib/index.js:2894-2895then throws, and the exception escapesrefreshBlockedMasks→ theagent/createdhandler at:129-141→ session creation.B — tool registration lands in the global layer.
dsh-tools.register()is documented as "Register globally or in the calling agent scope" and resolves the layer with the samescopeOf(:2876-2884). Degraded, every Agent's MCP tools are inserted into the global layer, so the second insertion tripsNamedEntries.duplicateError(:2634) — the stack quoted under Actual result. Because the provider hardcodesfailOnStartupError: true(mcp.js:102),dsh-mcp-clientswaps its defaultregistrationFailure: "contain"for"throw"(dsh-mcp-client/lib/index.js:489-493, rethrown at:155-157) and wraps it asinitial connection or tool synchronization failed(:832), which is what makes an otherwise contained conflict fatal.Why
mode: launchhides itThe provider makes the browser resource exclusive only in attach mode (
dsh-experimental-browser-use-playwright-mcp/lib/index.js:40):so under
mode: launch— the example in the provider README —available()short-circuits totruefor every Agent (dsh-experimental-browser-use-runtime/lib/types/index.js:45-49),statusis never'blocked', and line :65 is unreachable. The defect needs attach mode or two concurrent Agents to surface; with a single session nothing looks wrong. Ordinary MCP servers are also unaffected because they keep the defaultfailOnStartupError: false, which contains the same conflict.Workaround (confirmed working)
Replace the profile-local real copies of modules that are also provided by the host with directory junctions pointing at the host copy, so each module has exactly one physical file per process:
Applied to all five duplicated modules (
dsh-scope,dsh-mcp-client,cosmokit,dsh-util-values,schemastery); the three profile-only browser-use packages are correctly left alone, as the host has no copy to point at. Onlydsh-scopeis strictly necessary — the other four do not export identity tokens. Session creation recovers immediately.Two caveats: it must be re-applied after any
pnpm install/dsh plugin add, and it should touch only<profile>\node_modules\@deepseek-ai\*— do not wipe$DSH_HOME/profiles/node_modules, since the heal step rebuilds only the install closure and would drop third-party scopes.Suggested fixes (any of the three)
@deepseek-ai/dsh-scopeand@deepseek-ai/dsh-mcp-clientfromdependenciestopeerDependenciesin@deepseek-ai/dsh-experimental-browser-use-runtimeand re-publish — the identical treatmentdsh-tools,dsh-agentanddsh-browser-usealready receive in that same manifest. This removes the duplication at its source for every profile.packages/core/scope/src/index.ts:18→const kScope = Symbol.for('dsh.scope'). This is layout-independent and is the level the fix belongs at; it is also this repo's existing idiom (Schemastery usesSymbol.for('schemastery')). Note it covers theSymbolfacet only —dsh-scopealso keeps carrier state in module-local collections, so (a) and (b) are complementary, not alternatives.refreshBlockedMasksacatchso the documentedblockeddegradation cannot abortagent/created; do not let a per-Session provider mount turnsession/createinto a 500; and includecausein the wrapped MCP error, which currently sends diagnosis toward "connection" when the failure is registration.Related reports
kongminOS/deepseek-harness:fix/dsh-scope-symbol-for).dsh-scope's module-localWeakMapcarrier registry (isScopeCarrier()false for every dispatch).mode: launch, with the duplicate-registration stack.activeServerNamesis keyed process-wide while the tool registry is per-agent, i.e. thectx.rootfallback this report explains the precondition of.profiles/node_modulesfallback tree is rewritten per install anchor; do not delete it wholesale.Screenshots of the install layout, the two-copies check and the workaround's verification output are attached below.




All reactions