Bug: stale broken fallback symlink (@deepseek-ai/dsh-storage-json) breaks every DeepSeek request after 0.1.2-alpha.3 upgrade #5439
Replies: 1 comment
|
Outstanding diagnosis — I verified both defects end-to-end against the current alpha sources (repo HEAD 4e84901 = 0.1.2-alpha.4), and this thread is actually the fourth report in a known family. Line anchors below. Defect 2 confirmed, and it's a known family. Defect 1 confirmed, with one design nuance your fix should account for. On One more observation on your root-cause narrative: you're right that the stale link is leftover from the pre-upgrade graph — Net: your workaround (disable the plugin) is sound and loses only the observability field. For upstream, the two fixes are independent and both small: (1) inventory per-entry degrade (also closes #5172/#5173/#5196), (2) stale-symlink sweep wired into the current-check or run unconditionally, with target-existence + canonicalized comparison. If you open a PR for either, I'm happy to review the shape against these anchors. |
Uh oh!
There was an error while loading. Please reload this page.
Bug: stale broken symlink in profile module-fallback makes every DeepSeek request fail with "request extension preparation failed"
Summary
After upgrading
deepseek-harnessto0.1.2-alpha.3, every DeepSeek model request fails with:Root cause:
plugin-package-inventory-deepseek(enabled by default) fails to resolve thepackage identity of one active plugin entry —
@deepseek-ai/dsh-storage-json— becauseits symlink under the shared profile module-fallback (
$DSH_HOME/profiles/node_modules) is astale broken symlink left over from the pre-upgrade dependency graph.
healProfilesModuleFallbacknever repairs or removes it, and the inventory plugin treats the lookup failure as fatal for the
whole request.
Environment
0.1.2-alpha.3(upgraded in place from an older checkout)$DSH_HOME/profiles/node_modules(shared) mirrors theinstallation dependency closure; profiles load their plugin tree through
healProfilesModuleFallback.Reproduction
(The same failure occurs in the web profile, which is how it was first observed.)
Precise failure point
Instrumenting the real headless loader and running
plugin-package-inventory-deepseek'sper-entry
PackageIdentityResolver.resolve()over all 85 active non-group entries yieldsexactly one failure:
The failing lookup is
barePackageManifest()→createRequire(anchor).resolve.paths(packageName)→existsSync(join(searchPath, packageName, 'package.json')).The shared fallback symlink is broken:
A healthy neighbour for contrast (re-created after the upgrade at 2026-09-02 20:31):
Why the symlink is stale
resolveModuleFallbackEntries()(packages/boot/app-boot/src/profile.ts) builds the fallbackentry set by BFS over the dependency graph from
apps/cli/package.json(the install anchor).@deepseek-ai/dsh-storage-jsonis declared as a dependency ofdsh-base(andsession-controller,message-feedback,session-projection-cache,subagent) — i.e. it isreached through the profile bundle layer, not through the
apps/clidependency closure.After the upgrade it is no longer reachable from the install anchor's BFS, so it is absent
from
resolveModuleFallbackEntries()'s result.healProfilesModuleFallbackLocked()only iterates the currententries(creating/repairingthose symlinks) and never removes symlinks that are no longer in the entry set. The
pre-upgrade symlink therefore survives, still pointing at the old (now non-existent) path.
The entry still activates in the loader (Node resolves
@deepseek-ai/dsh-storage-jsonvia theworkspace source package
packages/storage/storage-json), so the plugin is present and ACTIVE —but
plugin-package-inventory-deepseek's independentexistsSync-based lookup walks theprofile fallback directory and hits the broken link.
Why this takes down every request
plugin-package-inventory-deepseek/src/index.ts—resolve()throws on the failed lookup:DeepSeekLlmApiExtensionRegistry.prepare()runsPromise.allover all registered providers,so the throw propagates.
llm-deepseek's adapter wraps it asLlmError('DeepSeek request extension preparation failed', 'REQUEST_EXTENSION')— a hardfailure of every chat/completions request, before any HTTP dispatch.
Two distinct defects
healProfilesModuleFallbackleaks stale symlinks. It has no sweep for entries thatdropped out of the resolved dependency closure, and
moduleFallbackEntryCurrent()comparesonly
readlinkSync(link) === entry.packageDir(a path-string equality) without checking thatthe target actually exists. After a dependency-graph change, stale broken links accumulate
and are never repaired or removed.
plugin-package-inventory-deepseekis fail-closed on a diagnostic field. A singleunresolvable package identity aborts the entire request. Since this field (
dsh_plugin_packages)is optional/observability-only, a failed lookup for one entry should degrade gracefully
(skip that entry, or omit the field) rather than fail the model request.
Suggested fixes
healProfilesModuleFallbackLocked(or its caller), remove any symlink under the sharedfallback
node_modulesthat is not present in the current entry set (or at least any that isbroken —
lstatis a symlink butstat/existsSyncfails).moduleFallbackEntryCurrent, forkind === 'symlink', also verify the target exists(
existsSync(join(entry.packageDir, 'package.json'))), so a broken link is treated as staleand healed.
plugin-package-inventory-deepseek, treat an unresolvable individual entry as a skip (orlog-and-continue) rather than throwing, so an observability field can never take down the
whole request path.
Workaround (applied locally)
Disable the plugin in each profile's
cordis.patch.yml:This restores all DeepSeek requests. It sacrifices the
dsh_plugin_packagesdiagnostic field,not any functional behaviour.
All reactions