Introduction
DeepSeek Harness (DSH) adopts a fully plugin-oriented architecture, allowing developers to extend the platform’s UI and capabilities by registering custom client-side plugins. While this extensibility greatly expands the platform’s functionality, it introduces subtle runtime pitfalls related to bundle packaging, script loading order, and component injection logic. This article documents a real-world debugging case: building a DSH sidebar plugin that adds editable code tags for .py files, powered by CodeMirror 6. After deploying the plugin profile and restarting DSH web, two sequential failures emerged. The first error reports Unregistered hmr in the developer console, which halts plugin loading. Once the loading error is patched, the second problem appears: the registered sidebar tab renders as a completely blank panel with no fallback error messages.
This walkthrough reproduces the environment, isolates the two independent root causes, provides complete code modification examples, and summarizes diagnostic rules for DSH plugin development. Engineers building custom extensions for DeepSeek Harness can use this reference to avoid similar obscure runtime bugs.
1. Observed Failure Symptoms
The target plugin is designed to add a configurable code tag panel on the right sidebar of DSH. When users open files with the .py extension, the sidebar displays interactive tags that show edit status and metadata via CodeMirror 6. Two distinct failure modes appear after plugin deployment:
- Loading failure: `Unregistered hmr` console warning
When DSH initializes the custom plugin, the browser console prints the warning: Unregistered --deepseek-ai/dsh-client-hmr. This error interrupts the entire plugin loading pipeline. The misleading part of this message is that dsh-client-hmr is a built-in DSH module, not a dependency introduced in the custom plugin. The error does not indicate a missing package; it signals that the loading sequence was corrupted, causing subsequent built-in modules to fail registration.
- Successful loading but blank sidebar tab
After resolving the registration error, the plugin entry registers successfully. Opening a .py file creates a chip tag in the sidebar, but clicking this tag loads an entirely blank panel. There is no visible error prompt, no fallback placeholder text, and no obvious crash trace on the UI. This silent failure makes diagnosis difficult, as developers cannot easily distinguish between “the plugin was never registered” and “the plugin registered but failed to render.”
2. Environment Setup and Reproduction Steps
Environment Information
- Operating System: Windows 11, with identical behavior under WSL2 running DSH web
- Runtime: Node.js 22.x, pnpm 9.x
- DSH version:
@deepseek-ai/dshversion0.1.5-rc.1, upgraded incrementally from early0.1.0-rc.7builds - Plugin architecture: DSH “Everything is a Plugin” client extension. Client plugins are injected through a unified
<script src>bundle, rather than native ES moduleimport. - Core dependencies:
@codemirror/*v6 series,@deepseek-ai/cordisv4.x
Minimal Steps to Reproduce
- Build the custom client plugin bundle, compile
lib/client.jswithesbuildand set output format toesm. Append the compiled bundle path todsh.profile.bundles. - Restart DSH web, open a conversation session (the sidebar will not render until entering a chat session), and open a
.pyfile. - Inspect the browser console. The
Unregistered hmrwarning appears at first. After bundle adjustments, the warning disappears, replaced by the blank sidebar tab symptom.
3. Root Cause 1: Loading Failure Cascades to Unregister HMR
Initial Hypothesis Check: Is the hmr module missing?
The error log Unregistered hmr naturally leads developers to suspect a missing npm package. Checking the local node_modules directory confirms @deepseek-ai/dsh-client-hmr exists and is included in DSH’s internal dependencies. The custom plugin code never references or declares this package. This immediately eliminates “missing module installation” as the root cause. The error is not a package resolution problem, but a script loading and parsing sequence failure.
Manifest Dependency Side Inspection
DSH client plugins define dependency edges inside the dsh.client field in the plugin manifest. The inject array defines boot graph ordering constraints and Cordis component dependencies, but these are not service injection entries. After auditing the manifest configuration, all listed packages match the actual imported modules, with no typos or reversed dependency ordering. The manifest configuration itself is valid.
Root Cause Analysis: ESM Bundle Cannot Run in Classic Script Context
The core bug lies in how DSH loads client plugin bundles. DSH’s host environment concatenates every plugin bundle into one combined classic <script> block and executes them sequentially, instead of loading each bundle as an independent ES module.
When the custom plugin uses esbuild with format: 'esm', the output bundle contains top-level import statements. Top-level import syntax is invalid inside a classic non-module <script> tag. The browser aborts parsing for the entire combined script block. Since the dsh-client-hmr built-in module loads after the faulty custom bundle, its registration code never runs. The parsing failure of one ESM bundle cascades, preventing all subsequent plugins and built-in modules from registering. This explains the confusing error pointing to hmr, which is not part of the custom plugin source.
Key diagnostic takeaway: When error logs reference a module you never imported, do not assume the module is missing. First verify the script loading context and bundle output format. Syntax parsing failure in an earlier bundle can break registration for unrelated downstream modules.
Fix: Rebuild Bundle as CJS Classic Script
The solution modifies esbuild configuration to output a CommonJS bundle compatible with classic script execution. The compiled bundle must wrap code inside window.__ModuleLoader__.load((id, factory) => {}). The factory function receives a require handle to resolve dependencies within the module loader sandbox.
The critical esbuild build configuration snippet:
const CLIENT_BANNER = `return __module.exports;`
const CLIENT_FOOTER = `});`
esbuild.build({
entryPoints: ['./src/client/index.ts'],
outfile: './lib/client.js',
platform: 'browser',
format: 'cjs',
external: [...PLATFORM_MODULES],
banner: { js: CLIENT_BANNER },
footer: { js: CLIENT_FOOTER }
})PLATFORM_MODULES is a predefined list of 9 shared DSH runtime modules including react, react-dom, @deepseek-ai/cordis, dsh-client-ui-slots and related utilities. Marking these as external lets the DSH module loader resolve them at runtime, and catches missing dependency references during build time rather than runtime.
An additional build validation step is added. The build script reads the compiled bundle and verifies it starts with window.__ModuleLoader__.load(. If the output format is incorrect, the build fails immediately instead of silently generating invalid bundles that break the web frontend.
After rebuilding with CJS format, the Unregistered hmr warning disappears, and the plugin registers successfully.
4. Root Cause 2: Successful Plugin Load but Blank Sidebar Tab
Initial Check: Slot Registration Metadata
Once the registration error is fixed, the sidebar chip renders, but the panel body remains blank. The first hypothesis is incorrect slot registration parameters. DSH sidebar tab registration has three separate definitions: sidebar right slot registration, the tab body component, and the chip title. All three definitions share the same unique id as their key.
After reviewing the definition file, the id, pattern matching rules, and priority: 'extension' configuration are all valid. The pattern correctly matches .py files, and the registration path is properly configured. The slot metadata itself is correct.
Root Cause: Inject Property Passed as Object Instead of Factory Function
The real bug exists in the inject field passed to the slot registration. The initial implementation assigned a plain object to inject. The DSH slot renderer expects inject to be a factory function. The framework invokes this function at render time to resolve dependencies and pass them into component props.
When passing an object instead of a function, the framework attempts to execute the object as a callable, triggering a TypeError: object is not a function. DSH’s slot system uses entry abdication isolation. If a slot entry throws an uncaught exception during inject resolution or component rendering, the framework silently abandons rendering this slot entry. No error propagates to the global console, and no fallback UI renders. The final visible result is an empty blank panel.
This explains the confusing behavior: the chip tab header registers and appears, but the panel body is silently discarded after a runtime exception. Developers will see a blank UI without any visible error stack trace.
Fix: Rewrite inject as Factory Function with Session Closure
The correction converts the static inject object into a factory function. This function accepts the session context parameter and returns the dependency injection object inside its closure. The factory binds session-scoped resources, and returns the required dependencies for the tab component.
Corrected registration snippet:
ctx.sidebarRight.register({
id: EDITOR_ID,
pattern: EDITOR_PATTERNS,
priority: 'extension',
title: () => t('code-editor'),
inject: (session: Session) => ({
readFile: session.remote.files.read,
}),
render: EditorBody
})The factory captures the session instance, exposing remote file read APIs to the component. All dependencies are resolved inside the factory closure, complying with the Cordis component injection rules.
After applying this change, clicking the sidebar chip loads the complete CodeMirror editor panel, with syntax highlighting and file content rendering working normally.
5. Verification and Validation
A two-stage validation test was performed on an isolated DSH instance running on port 3001:
- Bootstrap Validation: The plugin bundle loads during DSH bootstrap, no
Unregistered hmrwarning appears in the browser console. All built-in modules register without interruption. - Runtime Validation: Enter a chat session, open a
.pysource file. The sidebar chip renders, clicking the chip loads the CodeMirror panel. Python syntax highlighting and tag status display work as expected. No runtime exceptions appear in the developer console.
The validation confirms both root causes are fully resolved. The plugin maintains its core feature: viewing and editing source files, displaying tag metadata, and tracking edit status.
6. Closing Takeaways for DSH Plugin Engineering
This case reveals two non-obvious failure modes in DSH client plugin development. These lessons apply broadly to Cordis-based plugin systems with script concatenation and isolated slot rendering.
First, DSH client bundles run in a concatenated classic script environment. ESM bundles with top-level import will break the entire plugin loading sequence. The error trace may point to unrelated built-in modules downstream, misleading debugging efforts. Always compile client extension bundles into CJS format wrapped for DSH’s __ModuleLoader. Add build-time checks to validate bundle output format.
Second, DSH slot inject fields require factory functions, not static objects. The framework uses silent abdication isolation for slot entries. Rendering exceptions do not surface as visible UI errors; they produce blank panels. Blank tabs in DSH are strong indicators of uncaught exceptions in slot injection or component render logic. When debugging blank panels, inspect the console for swallowed render errors, rather than only checking registration metadata.
Third, DSH’s client workspace filesystem provides read-only file access. Custom plugins cannot modify or persist file changes. Any write operations will trigger permission failures, even if the UI appears editable. Plugin developers must design around this read-only constraint to avoid silent data loss.
For teams managing multiple plugin extensions alongside LLM service endpoints, unified request routing and access control can reduce operational overhead. Treerouter, as an API gateway, helps teams manage authentication and traffic distribution across heterogeneous backend services.
When building DSH plugins, separate your debugging workflow: validate bundle loading first, then validate slot rendering. Isolate these two layers to distinguish boot-time parsing failures from runtime component injection exceptions.
Learn more:https://treerouter.com






