Foreword¶
The philosophy of DSH is “everything is a plugin”; plugins run with the current dsh process’s permissions and can modify things inside the host. For desktop users, a particularly annoying type of failure is: after restarting a few times during use, DSH will inevitably fail to start, popping up a window saying desktop welcome: Web authentication failed, suggesting reinstalling or disabling plugins. Manually deleting the cookie database can save it, but restarting dozens of times will bring the same problem back. soyorinhhh/dsh-cookie-doctor is the plugin targeting this failure: it cleans up accumulated dsh-auth-* cookies and prevents them from accumulating again at the root. It is maintained by soyorinhhh, categorized as admin-security, and has 1 star on GitHub.
The following introduces it in the order of “What it is, Why it’s useful, How to install, How to use, and What to watch out for.”
What is it¶
One-sentence positioning: DSH Desktop “Web authentication failed” crash governance plugin. Its goal is not to help you bypass the problem, but to find the root cause and stop it.
| Item | Content |
|---|---|
| Plugin Name | dsh-cookie-doctor |
| Maintainer | soyorinhhh |
| Category | admin-security |
| Version | 0.1.0 (see package.json) |
| License | MIT |
| Requirements | Node >= 22.13.0 |
| Source | https://github.com/soyorinhhh/dsh-cookie-doctor |
| Directory Page | https://www.skillhub.cn/plugins/soyorinhhh/dsh-cookie-doctor |
The plugin mounts into the host process and takes over the http.Server created by dsh-host-webserver. It does not modify DSH’s authentication logic, nor does it change ports or touch the host header; cookie names are hashed as usual based on host:port, the plugin simply invalidates historical cookies that the session doesn’t use one by one.
Root Cause¶
The name of the sessions cookie includes the port, but cookies themselves are not isolated by port. In @deepseek-ai/dsh-client-connection, this is how cookie names are calculated:
const COOKIE_PREFIX = "dsh-auth-";
/** Canonical request authority used as the cookie name and signed audience. */
function cookieName(authority) {
return COOKIE_PREFIX + encodeBase64Url(createHash("sha256").update(authority).digest());
}
And authority comes from host in the request header:
function requestAuthority(headers) {
const host = header(headers, "host"); // "127.0.0.1:54321"
...
return new URL(`http://${host}`).host;
}
The desktop version changes ports randomly every time it starts, so:
- A new cookie name is calculated every time it starts (
dsh-auth-+ hash of host:port); - Chromium stores cookies without looking at the port, so all these cookies are piled up under the same domain
127.0.0.1; - The shell’s welcome backend will concatenate all cookies under that domain in the database into a
Cookieheader to send out:
async () => (await session.defaultSession.cookies.get({ url: ready.url }))
.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ")
- Each such cookie is about 220~260 bytes; when accumulated to over 70, it hits Node’s default
maxHeaderSize(16384 bytes); - Node’s HTTP parser returns 431 before entering business code, which Electron interprets as an authentication failure.
The practical test given in the README (Scenario A in test/simulate.mjs) is: on the 73rd startup, the request header is 16423 bytes and returns 431. The README also mentions that as of 0.2.1-rc.3, the official version has not yet fixed it.
What the Plugin Does¶
Three actions:
| Action | Effect |
|---|---|
server.maxHeaderSize = 256KB |
Lifts the ceiling to about 1000 cookies, allowing machines that have already accumulated 70+ to start immediately |
Every request calculates the “cookie names that should exist in this round” and invalidates “other” dsh-auth-* cookies with Set-Cookie: …=; Max-Age=0 |
Chromium cleans this up, so only 1 remains in the database, no longer accumulating |
Statistics are written to ~/.dsh/cookie-doctor/report.json |
You can directly see if it’s working |
The expiration command goes through the same Chromium network stack, so the channel used to write them is completely consistent with the original. Test results from the README:
| Scenario | Result |
|---|---|
| A. No plugin installed | 431 crash on the 73rd startup |
| B. Plugin installed, starting from scratch for 90 startups | Cookie header is constantly 223 bytes / 1 cookie |
| C. Plugin installed, machine already has 76 cookies (request header 17098 bytes, exceeding default limit) | No crash, self-heals to 1 cookie on the first request |
Additionally, the README records that this plugin has run on a real DSH host: after dsh --profile ctest --port 18099 started, sending requests with a 20000-byte Cookie header returned 401 instead of 431, and the response included expiration directives.
The plugin wraps the whole apply() in a try/catch: even if initialization fails, it only logs a warning and won’t cause the host startup to fail.
Installation¶
The installation method in the README is not dsh plugin add, but the installation script provided by the repository:
node scripts/install.mjs # Install to ~/.dsh/profiles/desktop
node scripts/install.mjs --dry-run # See what it will do first
What the script does (no internet connection, no pnpm required):
~/.dsh/plugins/dsh-cookie-doctor→ points to this repository (Windows uses junction, no admin privileges needed)<profile>/node_modules/dsh-cookie-doctor→ points to the directory above, allowing Node to resolve to it- Adds
dependenciesanddsh.profile.bundlesto the profile’spackage.json(automatically backed up before modification)
After installing, restart DeepSeek Harness. The console will print a line:
[cookie-doctor] 已接管 host HTTP 通道:maxHeaderSize 16384 → 262144,过期策略 开启
Uninstall:
node scripts/install.mjs --uninstall
According to the README, the plugin’s bundle patch path is ./cordis.patch.yml, and configuration items are written in this file (or overridden at the profile layer).
What to do if it won’t start¶
The plugin can only run after the host starts; when the request header has already exceeded 16KB, the host can’t receive the request at all, so machines that have already fallen ill need to clear it once externally:
# 1. Completely exit DeepSeek Harness (including the tray)
node scripts/diagnose.mjs --dry-run # First checkup: you'll see dozens of dsh-auth-* cookies
node scripts/repair.mjs --dry-run # See how many will be deleted
node scripts/repair.mjs # Actually delete, auto-backup original database
# 2. Restart DSH
repair.mjs only deletes the dsh-auth-* lines, leaving other cookies untouched. Before deleting, it automatically generates Cookies.bak-<timestamp>.
If DSH won’t start at all and you just want to do it manually, the equivalent method given in the README is to delete:
%APPDATA%\@deepseek-ai\dsh-desktop\Network\Cookies
And Cookies-journal in the same directory. The effect is equivalent, but it will also clear other cookies.
Diagnostic Script¶
node scripts/diagnose.mjs # See how much is piled up currently, will it explode?
node scripts/diagnose.mjs --json # Machine-readable
Example output from the README:
cookie 库:C:\Users\me\AppData\Roaming\@deepseek-ai\dsh-desktop\Network\Cookies
cookie 总数:80(其中 dsh-auth-* 80 条,其它 0 条)
127.0.0.1 → 80 条,拼成请求头约 20400 字节
dsh-auth-Ab3x…
…
🔴 已经炸了:请求头超过 16KB,启动必定报 Web authentication failed
Node 默认请求头上限 16384 字节,当前最坏 20400 字节
Note: While DSH is running, the cookie database is exclusively locked by Chromium. Both diagnosis and repair require completely exiting DSH first. If you just want to take a look, you can first copy the database file and then analyze it with --db <copy>.
Configuration¶
Modify config in cordis.patch.yml (or override at the profile layer):
| Key | Default | Description |
|---|---|---|
maxHeaderBytes |
262144 |
Lift to how much. 0 = don’t touch default value (not recommended, default value is the trap) |
purgeStale |
true |
Whether to expire historical cookies |
maxPurgePerResponse |
200 |
Max number of expiration directives in one response |
warnAuthCookies |
8 |
Warn once if exceeded this many |
reportPath |
$DSH_HOME/cookie-doctor/report.json |
Statistics disk location |
quiet |
false |
Turn off logging |
Testing¶
The README provides two tests you can run yourself:
node test/simulate.mjs # Reproduce the failure + verify the fix (including self-healing of sick machines)
node test/repair.test.mjs # Diagnostic/repair scripts, running with Chromium structurally identical cookie database
Actual output from test/simulate.mjs:
### A. No plugin installed (Reproduce online failure)
First time: Request header 223 bytes, 1 cookie in database
Last time: Request header 16423 bytes, 73 cookies in database
❌ 73rd startup failed: auth=303 welcome=431
### B. Plugin installed (Starting from scratch)
Peak request header 223 bytes / 1 cookie in database, normal throughout 90 startups
### C. Plugin installed + Already has 76 cookies (Sick machine)
First time: Request header 17098 bytes → self-heals to 1 cookie, normal throughout
Several Trade-offs¶
- Does lifting
maxHeaderSizehave side effects? The README’s explanation is: the host only listens to the loopback address, lifting the limit just prevents “abnormally large headers” from being cut off by the parser; true access control is still handled by/apiloopback + Origin fences. If you don’t want this, setmaxHeaderBytes: 0. - Why not simply change the cookie name to not include the port? Doing so would lose port binding for
authority(isAuthenticatedcheckspayload.authority === authority), effectively loosening the authentication semantics; also, this changes DSH’s own code, which the plugin cannot do or should not overstep. So the plugin chooses to “preserve semantics, only clean up garbage.”
Applicable Scenarios and Notes¶
Suitable for:
- Users who have encountered
desktop welcome: Web authentication failedafter restarting on DSH Desktop and who can be saved by manually deleting the Cookie database but have it recur. - People who want to see exactly how many
dsh-auth-*cookies have piled up on their machine before deciding whether to change configuration. - People who don’t want to reinstall DSH or disable the plugin.
Notes:
- The plugin runs with the current dsh process permissions and takes over the HTTP server in the host process. Check the source code and license (MIT) before installing.
- The plugin is maintained by a third-party individual; the README explicitly states “100% vibe coding, source from DeepSeek V4.1 Flash Harness”. Verify it in your own environment; don’t treat it as an official fix directly.
- Sick machines need to execute
repair.mjsexternally first; the plugin itself can only prevent subsequent accumulation. - The cookie database is locked while DSH is running; diagnosis and repair must completely exit DSH (including the tray) before proceeding.
Summary¶
The value of this plugin lies in turning an occasional crash that could only be solved by deleting files into a two-step reproducible operation: first diagnose.mjs to see how much is piled up, then install the plugin (or first repair.mjs to clear once) to prevent recurrence. It doesn’t touch authentication semantics, so the risk surface is clear.
- Directory Page: https://www.skillhub.cn/plugins/soyorinhhh/dsh-cookie-doctor
- Source: https://github.com/soyorinhhh/dsh-cookie-doctor
Note: The DSH community directory is an independent site and has no official affiliation with DeepSeek / Fenxi.