DeepSeek Harness shared package contract regressions

TroubleshootingPublished 2026-10-20Author: DeepSeek Plugin Market
DeepSeek Harnessshared packagesdeepEqualJsonmarketVersioncontract regression
DeepSeek Harness packages break their contracts: deepEqualJson reads the prototype chain and marketVersion only checks a regex.

None of these five problems is a misused API; in each case a shared package behaves differently from the contract it declares, so upgrading will not make them disappear. A user reviewed them one by one in read-only mode on 687f282e86 and concluded that 5 out of 5 are real defects. The patches and regression tests live on a fork branch and have not been merged upstream (Discussion #10065).

DeepSeek Harness shared package defects: what each one gets wrong

The common thread is that validation or comparison is narrower than the documentation, and each case can be triggered with a minimal input. Item by item:

  1. deepEqualJson in values compares records with in plus member reads. Expected check: {"__proto__":{},"b":2} compares equal to {"a":1,"b":2} because an inherited member satisfies the left-hand a. The fix compares own properties only.
  2. marketVersion in plugin-market-index uses only a SemVer syntax regex. Expected check: an overlong version passes validation, but sorting the list with semver.rcompare throws a bare TypeError instead of MarketIndexError.
  3. PartialArguments#invalid in values reads the value without going through remember. Expected check: an invalid state discovered after the read does not trigger refresh(), unlike the behavior of complete().
  4. normalizeLlmFailure in llm renders non-Error throws. Expected check: throwing {message, code} turns the error message into [object Object] and loses the original message.
  5. boundContextSummary in llm truncates by code unit. Expected check: when the cut lands inside a surrogate pair, the summary keeps a lone high surrogate and the message carries it into the persistence log.

The patch branch also corrects two pieces of documentation: the code-language JSDoc claims hcl maps to tf, while the actual table still maps hcl to hcl; and the plugin-market-index README claims no runtime dependencies, while the validator depends on semver and a package-manifest package. According to the thread, the reviewer considers the second description to count one more package than the code does.

The five defects have very different trigger surfaces, so triaging by input is safer than changing all five at once. Four steps:

  1. Check whether your JSON input can contain keys such as __proto__ or constructor. Expected: only such input hits deepEqualJson; ordinary object comparison is unaffected.
  2. Check whether the market index contains overlong or edge-case version strings. Expected: normal SemVer never triggers the marketVersion sorting exception.
  3. Check whether you depend on a callback after PartialArguments#invalid changes. Expected: callers that never read the field cannot reach the third defect at all.
  4. Search logs for [object Object] and replacement characters. Expected: the former points to a non-Error throw, while the latter points to summary truncation.

The value of triage is that most applications hit only one or two of these, and changing all five widens the regression surface for no benefit.

DSH plugin path 1: merge the patch branch

This suits people who can modify host source and want all five fixed in one pass. How to do it:

  1. Fetch the fork branch fix/shared-contract-regressions-20261017. Expected: five separate commits, one for each defect above.
  2. Read the commit messages and cherry-pick only what you need onto your baseline. Expected: each commit touches one package, so conflicts stay contained.
  3. Run the tests for the package and for affected consumers. Expected: the new regression cases fail on the unfixed source and pass after the fix.
  4. Record your baseline commit and the cherry-pick order. Expected: on the next host upgrade you can tell whether upstream absorbed these patches.

The five commits are fc74097e0e (deepEqualJson), 038c710d33 (marketVersion), b49f975945 (invalid observation), d451d26f9b (non-Error throws), and 61ee3f668e (surrogate truncation). According to the thread, all of them include regression tests.

DeepSeek Harness path 2: patch individual packages locally

This suits people who do not want to replace the host and only need to fix the one or two packages they actually hit. Steps:

  1. Decide which package really affects your feature; do not change all five. Expected: a list of one or two package names and the scenario that triggers them.
  2. Override that package's build output with your existing patch mechanism, touching only the comparison or validation logic. Expected: the package version is unchanged and behavior changes as intended.
  3. Add a minimal case for each change, using the JSON or version string from the checks above. Expected: the case fails without the patch and passes with it.
  4. Re-apply the patch and re-run the cases after a host upgrade. Expected: the patch still applies, or upstream fixed it and you can delete the patch.

The cost of local patches is rechecking offsets on every upgrade, especially line numbers inside minified output. Assert on behavior rather than line positions and you avoid much of that rework.

DeepSeek Harness path 3: add defenses at the call site

This suits people who cannot change dependencies at all and only need their own plugin to stop misbehaving; before publishing a plugin on DSH Plugin Hub, confirm these package behaviors cannot break it. Three common defenses:

  1. Normalize to own properties before comparing JSON, for example by taking only what Object.keys enumerates. Expected: members on the prototype chain no longer affect equality.
  2. Before using the return value of marketVersion, run semver.valid yourself and enforce a length limit. Expected: overlong versions are rejected before sorting and the bare TypeError never happens.
  3. When catching a non-Error throw, check for an own string message before falling back to String(value). Expected: [object Object] no longer appears in logs.

Call-site defenses do not fix the shared packages, and other callers still hit the defects. Their value is containing the failure at your boundary while you wait for an upstream merge.

DeepSeek Harness: use regression tests to prove the patch works

A code diff is not enough; each of the five needs a minimal input that can fail on its own. Build the tests in order:

  1. Write an equality assertion for deepEqualJson with a __proto__ key. Expected: on unfixed code the assertion passes (wrongly equal); after the fix it fails (correctly unequal).
  2. Write an overlong version case for marketVersion that asserts a MarketIndexError. Expected: unfixed code raises TypeError.
  3. Write a case that reads invalid, changes the content, and counts refresh calls. Expected: unfixed code does not refresh.
  4. Write one string assertion each for non-Error throws and surrogate truncation. Expected: the first no longer contains [object Object], and the second no longer contains a lone surrogate.

These cases must fail against the current source; otherwise they protect nothing.

DeepSeek Harness: notes for handling contract defects

Separate "the documentation is wrong" from "the implementation is wrong", because the fixes differ. Four points:

  1. When documentation is stronger than code, whether to fix docs or code depends on whether callers already rely on the documented semantics. If someone writes calls against the docs, fix the code.
  2. Fixing a shared package requires running consumer tests too; the change surface in values and llm is larger than it looks.
  3. When external PRs are not accepted, a fork branch plus a discussion thread is the viable submission route for now. Do not leave the patch only on your machine.
  4. After a host upgrade, recheck whether these commits were absorbed, so the same logic is not changed twice.

According to the thread, none of the five has entered an official release; the state is "patch available, merge pending". Writing them into your compatibility notes as known boundaries is more practical than waiting for an uncertain fix date.

The three paths differ as follows:

  1. Merge the patch branch: changes five packages at once, needs conflict resolution on upgrade, and suits people who can modify host source.
  2. Patch individual packages: changes only the one or two you hit, at the cost of rechecking build output offsets on every upgrade.
  3. Defend at the call site: touches no dependencies, but you maintain the defense yourself and other callers still hit the defects.

They are not mutually exclusive. A common order is to stop the bleeding with call-site defenses, then cherry-pick the commits you need. If you maintain several host branches, keep a per-branch list of merged commits so upgrades do not reopen settled patches.

Source: Discussion #10065, fix/shared-contract-regressions-20261017. For more plugin-side checks, see DSH troubleshooting.

FAQ

Why does DeepSeek Harness deepEqualJson report two different JSON objects as equal?

DeepSeek Harness compared records with in checks plus member reads, so members inherited from Object.prototype also satisfy a key on the left side. As a result {"__proto__":{},"b":2} compares equal to {"a":1,"b":2}. The fix is to compare own properties only; the commit on the patch branch is fc74097e0e.

What goes wrong when DeepSeek Harness plugin-market-index marketVersion only runs a regex?

DeepSeek Harness accepts overlong version numbers and lets sorting throw a bare TypeError instead of the documented MarketIndexError. The regex checks syntax only, while the actual sort uses semver.rcompare, which also limits total length and each numeric identifier. The patch validates with node-semver instead.

Why does throwing a non-Error object in DeepSeek Harness turn the message into [object Object]?

DeepSeek Harness normalizeLlmFailure renders the thrown {message, code} as a plain object string. The patch reads an own, data-backed message field without triggering accessors and keeps code as UNKNOWN, because third-party error codes are outside the harness classification.

What is the visible impact of DeepSeek Harness boundContextSummary splitting a surrogate pair?

DeepSeek Harness writes a summary to the persistence log with a lone high surrogate, shown as a replacement character or garbled text, and later code that walks by code point can become misaligned. The fix drops the unpaired half when the cut lands inside a surrogate pair.

Related Terms

deepEqualJson
deepEqualJson is the values package function that compares two values with JSON semantics. A correct implementation compares own properties only and must not let members from the prototype chain affect equality.— https://github.com/deepseek-ai/deepseek-harness/discussions/10065
marketVersion
marketVersion is the plugin-market-index function that validates the market index version. It must satisfy both SemVer syntax and the range the sorting implementation accepts.— https://github.com/deepseek-ai/deepseek-harness/discussions/10065
PartialArguments#invalid
PartialArguments#invalid is the read-only answer in the values package that says whether streaming arguments are already invalid. Reading it should register an observation like complete(), or later invalid changes do not trigger a refresh.— https://github.com/deepseek-ai/deepseek-harness/discussions/10065
surrogate pair
A surrogate pair is the UTF-16 encoding of one supplementary-plane character as two 16-bit code units. Truncation by code-unit index must never keep only one half.— https://github.com/deepseek-ai/deepseek-harness/discussions/10065

Sources