[Bug] Web UI stuck on "HARNESS Loading plugins…" — profile module fallback fails to resolve pnpm workspace symlinks (Windows) #3528
Replies: 1 comment 1 reply
|
This report matches the rc.8 ownership chain: One extra safety boundary: avoid repairing the missing client rows with a broad install inside the profile. A nearer copy of Cordis, Agent, Session, or Tools can make Web boot appear fixed while splitting core service identity and breaking native/MCP tool calls. For a source-owned fix, rebuild the fallback in a fresh I turned the full four-layer proof (bundle roster → fallback link → https://sandbaseai.github.io/deepseek-harness-handbook/web-loading-plugins-pnpm.html |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Summary
Running
dsh webfrom a source checkout (pnpm workspace monorepo) on Windows, the browser page athttp://127.0.0.1:3080stays on the boot screen "HARNESS / Loading plugins…" forever. There is no error in the browser console, no failed JS exception, and the server-side boot spinner reaches 100% — the page just never mounts.Root cause:
packageDirFromAnchor()inpackages/boot/app-boot/src/profile.tsdoes not resolve symlinks before computing Node module lookup paths, so packages linked by pnpm's isolated symlink layout via transitive workspace dependencies are never discovered byhealProfilesModuleFallback(). As a result the client boot manifest is missing UI plugins — including@deepseek-ai/dsh-client-ui-renderer— andmountApp()hangs forever waiting for theuiRendererservice that is never provided.Environment
node_modules)pnpm install && pnpm run build)pnpm dsh webSymptom
dsh webstarts normally, server listens on127.0.0.1:3080.page.fail()is never triggered and no failure message is shown./plugins/@deepseek-ai/dsh-client-ui-renderer/client.js/plugins/@deepseek-ai/dsh-client-ui-brand-official/client.js/plugins/@deepseek-ai/dsh-client-ui-reference/client.jswindow.__DSH_BOOT__in the servedindex.htmlis missing those packages even though they are declared inpackages/bundle/web-app/cordis.patch.yml.Root cause analysis
The chain, step by step:
Fallback healing walks the dependency closure. On every start,
healProfilesModuleFallback()(packages/boot/app-boot/src/profile.ts) BFS-walks the dependency closure starting from theapps/cliinstall anchor and creates flat symlinks under$DSH_HOME/profiles/node_modules/so that the profile directory (used asctx.baseUrl) can resolve every composed plugin.pnpm links workspace packages as symlinks.
apps/cli/node_modules/@deepseek-ai/dsh-web-appis a symlink topackages/bundle/web-app/.packageDirFromAnchor()uses the symlink path as-is. When the BFS visitsdsh-web-app, it callscreateRequire(anchor).resolve.paths(packageName)whereanchoris the symlink path. Node'sresolve.paths()computes lookup paths from the literal path — it does not resolve symlinks — so the returned paths climb up fromapps/cli/node_modules/...and never includepackages/bundle/web-app/node_modules/, which is wheredsh-web-app's own dependencies (@deepseek-ai/dsh-client-ui-*) are actually linked.Transitive UI plugins are never discovered. Consequently the symlinks for
dsh-client-ui-renderer,dsh-client-ui-brand-official, anddsh-client-ui-referenceare missing from$DSH_HOME/profiles/node_modules/@deepseek-ai/. (In my casedsh-client-ui-attachmenthappened to be reachable through another dependency chain, which is why only 3–4 of the UI plugins go missing.)Boot manifest excludes the plugins.
ClientModuleRegistry.resolveMeta()(packages/client/modules/src/index.ts) resolves each plugin'spackage.jsonanchored atctx.baseUrl(the profile directory). For the missing packages therequire.resolvefails, so they are silently excluded fromwindow.__DSH_BOOT__.mountApp()hangs forever. Inpackages/client/web/src/boot.ts,mountApp()doesctx.inject(['uiRenderer'], ...). Since theui-rendererclient bundle 404s, theuiRendererservice is never provided and the injection never resolves — the boot page stays at "Loading plugins…" indefinitely, with no error surfaced to the user.Suggested fix
Resolve the symlink before computing lookup paths in
packageDirFromAnchor():import { createRequire } from 'node:module' import { - existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, symlinkSync, unlinkSync, writeFileSync, + existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, realpathSync, symlinkSync, unlinkSync, writeFileSync, } from 'node:fs' function packageDirFromAnchor(anchor: string, packageName: string): string | undefined { + let realAnchor: string + try { + realAnchor = realpathSync(anchor) + } catch { + realAnchor = anchor + } /* v8 ignore next */ - for (const searchPath of createRequire(anchor).resolve.paths(packageName) ?? []) { + for (const searchPath of createRequire(realAnchor).resolve.paths(packageName) ?? []) { const candidate = join(searchPath, packageName) if (existsSync(join(candidate, 'package.json'))) return candidate }Full patch attached.
Verification
After applying the fix:
healProfilesModuleFallback()creates all missing symlinks under$DSH_HOME/profiles/node_modules/@deepseek-ai/.window.__DSH_BOOT__contains all 42 client plugins (previously 38–39)./plugins/@deepseek-ai/dsh-client-ui-renderer/client.js: 404 → 200 (39,268 bytes). All 42 plugin bundles + main entry return 200.Standalone repro of the lookup-path bug (no DSH needed):
Extra observation
Because the failure mode is "a service that is never provided" rather than an error, the boot page gives zero feedback. It might be worth making
mountApp()(orctx.injectforuiRenderer) time out and surface a diagnostic message — "required client plugin X did not load" — which would have saved hours of debugging.fix-profile-symlink-resolution.patch
All reactions