Introduction¶
When developing or loading plugins in DeepSeek Harness, a single tool scheduling crash (e.g., Cannot read properties of undefined (reading 'prepare')) leaves orphan tool_calls in the session logs. Subsequent requests are rejected with 400 INVALID_REQUEST; retries are futile, the session freezes, and the only option is to abandon the session.
The dsh-messages-sanitizer introduced below is designed for this exact problem: it automatically repairs the messages array at runtime to make it valid, allowing the conversation to continue.
How the Problem Occurs¶
The OpenAI-compatible protocol requires tool calls to appear in pairs, and tool messages must be immediately adjacent to their assistant tool_calls message:
assistant { content: ..., tool_calls: [{ id: "call_A", ... }] }
tool { tool_call_id: "call_A", ... } ← Must follow immediately, covering each id
When a tool scheduling crash occurs between “recording assistant tool_calls/tool/call” and “producing tool results”, the session logs are left with orphan tool_calls without a tool message response. The history reconstructed for the next request looks like this:
[..., assistant{tool_calls:[write]}, user{...}] ← Invalid
The API returns 400 INVALID_REQUEST directly, and retries use the history unchanged and are repeatedly rejected. If there are multiple retry failures after the crash, multiple duplicate user messages are left in the logs, straddling the orphan assistant and the injection point—by this time, even “inserting a tool message” cannot satisfy the adjacency constraint.
What is This¶
dsh-messages-sanitizer is a DeepSeek Harness plugin by Leeminjing, under the MIT license, version 0.1.1. In one sentence: it automatically repairs invalid tool_calls/tool message pairs (orphan tool_calls) in the messages array, preventing the session freeze caused by 400 INVALID_REQUEST.
Similar issues are tracked in the official DeepSeek Harness repository: Discussion #4843 describes the problem where the chat-completions interface returns 400 when there are unpaired results or incomplete tool_calls (missing id/name/arguments) in the session history. The official fix addresses this from the harness source code level. This plugin is complementary, not a replacement, for that fix: it does not modify DSH source code and only automatically corrects the messages array at runtime, acting as a safety net. Polluted old sessions, or users running on harness versions that still contain this bug or tool scheduling crashes, can be automatically rescued and the conversation continued without waiting for a harness release.
Four-Layer Repair Mechanism¶
The plugin works in four layers based on the timing of processing.
1. Auto Resume (agent/status, main path)¶
After a tool scheduling crash, while the agent returns to idle, it sends a synthetic error tool-result to the message box and wakes it up. The error is reported to the LLM as-is; whether the next step switches tools, retries, or reports to the user is decided by the LLM itself. This ensures AI messages are always the last one, preventing the dialogue from freezing.
2. Prevention (agent/pre-step)¶
Tracks calls that are “declared but never responded to by tool/result” in each session; before building the next request, a synthetic error tool-result message is inserted at the very front of that step’s messages (overwriting the old orphan after recovery). The synthetic message is persisted via user/message events with decision.messages, and deriveMessages() restores validity from the root, eliminating 400s at the source.
3. Cure (agent/request-error)¶
If the API still returns 400 due to tool_calls pairing/adjacency violations (e.g., old sessions already polluted, or expired messages straddling the orphan assistant), the plugin uses surface replacement to complete the fix and then forces a retry (retry is based on rebuilding the request from the repaired logs). Specific actions:
- Rewrites the dangling assistant message into a version without
tool_calls(stripping unresponded calls); - Neutralizes the orphan tool message into a plain text user message;
- Restores the assistant that was mistakenly stripped but whose result is still adjacent (restoring
tool_calls, preserving historical tool context); - Collapses duplicate user messages left by crash retries.
The fix is idempotent: encountering the same violation again does nothing, naturally falling back to downstream strategies, preventing infinite retries.
4. Fallback (llm/stream)¶
Performs pure array correction on every request, including pairing, adjacency reordering, discarding orphans/duplicates, and discarding empty assistants. Cyclically built requests are frozen (only alerting, not rewriting); non-frozen requests (like compaction, session-title, etc., that build their own messages) are directly replaced in place.
Installation and Enablement¶
First execute the installation command, then restart harness for it to take effect (the plugin is automatically loaded with the profile layer stack):
dsh plugin --profile web add github:Leeminjing/dsh-messages-sanitizer
Update to the latest version:
dsh plugin --profile web update dsh-messages-sanitizer
There is only one configuration item: enabled (boolean, default true), which is the master switch. When disabled, delete the inserted line in cordis.patch.yml or change it to:
- insert:
- id: messages-sanitizer
name: 'dsh-messages-sanitizer'
disabled: true
Verification¶
The plugin comes with tests covering pure function correction, session tracking, request failure repair, and real cordis/Session integration:
cd dsh-messages-sanitizer
node --test # 40 test cases in total
All tests use the real @deepseek-ai/dsh-session foldSurface / Session for validation, including end-to-end simulation of real crash sequences, idempotent verification after repairing polluted logs, surface replacement execution on real Session, and request failure repair which only intervenes when tool_calls pairing results in a 400, forcing a single retry after repair without infinite retries.
Applicable Scenarios and Considerations¶
Suitable for two types of people: developers who have developed plugins on DeepSeek Harness and encountered tool scheduling crashes that froze sessions; and users who still have polluted old sessions they want to rescue. The latter does not need to wait for a harness release; the cure path automatically strips dangling calls and retries successfully upon the first request failure.
Points to note before installation and use:
- The plugin runs with the current dsh process permissions; the source code and license (MIT) should be checked before installation.
- Requires Node >= 18. The plugin is a zero-build pure ESM; modifying plugin code requires no rebuild, just restarting harness (or letting cordis HMR reload).
- Runtime peer dependencies:
@deepseek-ai/cordis,@deepseek-ai/dsh-agent,@deepseek-ai/dsh-llm,@deepseek-ai/dsh-session,@deepseek-ai/schemastery. Same source as harness runtime. - The node_modules under the repository directory is a junction pointing to the harness runtime
~/.dsh/profiles/node_modules, provided solely for dependency resolution in localnode --test; the harness runtime does not depend on it. - It is complementary, not a replacement, to the source code fix for official DeepSeek Harness Discussion #4843: the official fix cures the problem at the DSH source code level, while this plugin does not modify DSH source code and only automatically corrects the messages array at runtime, capable of rescuing polluted old sessions.
Conclusion¶
dsh-messages-sanitizer solves a very specific deadlock: a single tool scheduling crash renders the entire session unable to continue. It repairs the messages array back to validity at runtime through four mechanisms: auto resume, prevention, cure, and fallback. The fix is idempotent and does not introduce new circular issues. The plugin directory page is at https://www.skillhub.cn/plugins/Leeminjing/dsh-messages-sanitizer , and source code and README can be found at https://github.com/Leeminjing/dsh-messages-sanitizer .