dsh-reaper
Manifest validFind and reclaim the orphan dev servers and watchers an agent session left behind.
dsh-reaper
Reclaim the ports your agent forgot to give back.
An agent starts vite, next dev, a watcher, a tunnel. The turn ends. The session ends. The process does not — it keeps holding 5173, and the next run dies with EADDRINUSE while you go hunting through Task Manager for a node.exe you cannot identify.
dsh-reaper inventories every listening endpoint on the machine, attributes each holder to this session, shows you exactly what it would kill, and only then — when you say confirm: true — reclaims the ports.
$ /reap
dsh-reaper · win32 · 2026-10-16T10:57:26.626Z
workspace: E:\work\shop-frontend
51 listening endpoint(s) · 432 process(es) · 3 reclaimable
5173 tcp agent-spawned pid 8899 18m node.exe
node E:\work\shop-frontend\node_modules\.bin\vite --host
3001 tcp workspace pid 9120 42m node.exe
node E:\work\shop-frontend\server.js
3306 tcp system pid 9168 9h29m mysqld.exe
↳ port 3306 is a well-known service port
19387 tcp protected pid 11664 2h9m DeepSeek Harness.exe
↳ holds the harness process tree
Why it is not just "kill by port"
Port tools tell you what holds a port. The hard part is deciding whether you are allowed to kill it, and that answer depends on facts a port number does not carry:
- Did this process descend from the harness that is running the scan? Killing it takes down the session.
- Is it the machine's own infrastructure —
mysqldon 3306,nginxon 80? - Is it a leftover dev server with no session tie at all?
- Is it something the user explicitly said to leave alone?
dsh-reaper answers that per endpoint and grades the result, so a --all sweep can be safe:
| verdict | meaning | auto-selected by all: true |
|---|---|---|
agent-spawned | descends from the harness host process | ✅ |
workspace | command line names the current workspace | ✅ |
dev-server | looks like a dev server, watcher or tunnel | ❌ |
unattributed | listening, but nothing ties it to this session | ❌ |
system | OS process, or holds a well-known service port | ❌ |
protected | matched neverKill / protectPorts, or holds the harness tree | ❌ |
Two rules make that table trustworthy:
- The harness never kills itself. The scan walks up from its own pid and refuses the whole ancestor chain, plus the process tree it is running inside.
- Infrastructure is not "unattributed". A holder of a well-known service port is graded
system— the same hands-off grade as an OS process — soall: truenever reaches it, and its note saysleft alonerather than hinting at an override.
Safety properties
Reaping is destructive, so the defaults are conservative and the refusals are absolute:
- Nothing is signalled without
confirm: true. The default call computes the plan and returns it. - The plan is the full descendant closure. Killing an
npm run devshim without its child leaves the port held — the exact failure this plugin exists to prevent. Descendants are ordered deepest-first so children die before their parents. - Refusals cannot be overridden by naming a pid. The harness process tree, pid 0–4,
neverKillmatches andprotectPortsholders are refused even when passed explicitly, with the reason reported. SIGTERMfirst,SIGKILLonly if you ask.force: trueescalates, and even then survivors are reported rather than hidden.- The result is verified, not assumed. After signalling, the plugin re-probes each freed port and reports
ports confirmed freeorports still held.
Install
dsh plugin add https://github.com/11111353535/dsh-reaper
From a checkout instead of a git host, pass the absolute path:
dsh plugin add /absolute/path/to/dsh-reaper
The package declares no dsh-* peer dependency on purpose: it imports only node:* plus @deepseek-ai/dsh-tools, which the harness resolves from its own installation. Nothing is bundled that could shadow the host's copy.
To tune it per profile, add a config block to the row the installer inserted in ~/.dsh/profiles/<profile>/cordis.patch.yml:
- insert:
- id: reaper
name: 'dsh-reaper'
config:
neverKill: ['my-precious-daemon']
protectPorts: [80, 443, 19387]
includeUdp: false
graceMs: 3000
scanTimeoutMs: 20000
| key | default | meaning |
|---|---|---|
neverKill | [] | Process-name or command-line substrings that must never be signalled. |
protectPorts | [] | Ports whose holder is refused even when named explicitly. The harness's own web port is added automatically. |
includeUdp | false | Also enumerate UDP endpoints. Off by default: dev servers are TCP. |
graceMs | 3000 | How long SIGTERM gets before the process is reported as surviving. |
scanTimeoutMs | 20000 | Wall-clock ceiling for one system enumeration. |
Tools
reaper_scan
Read-only inventory. Signals nothing. Grades every listening endpoint and returns the full scan.
| argument | type | meaning |
|---|---|---|
includeUdp | boolean | Also enumerate UDP endpoints. |
workspace | string | Workspace root used for attribution. Defaults to the session workspace. |
reaper_reap
Compute a plan and, only with confirm: true, execute it.
| argument | type | meaning |
|---|---|---|
pids | integer[] | Explicit root pids. Their descendants are included automatically. |
ports | integer[] | Reap whatever holds these ports. |
all | boolean | Also select every endpoint confidently attributable to this session (agent-spawned and workspace only). |
force | boolean | Escalate to SIGKILL for processes that ignore SIGTERM. |
confirm | boolean | Actually signal the plan. Defaults to false. |
reaper_free_port
Reclaim one specific port. If the port is already free it says so and does nothing; if the holder cannot be attributed or is protected, it reports why instead of guessing.
| argument | type | meaning |
|---|---|---|
port | integer (required) | The TCP port to reclaim. |
force | boolean | Escalate to SIGKILL if SIGTERM is ignored. |
confirm | boolean | Actually signal the holder. Defaults to false. |
Slash command
/reap # inventory
/reap --all # preview everything confidently attributable to this session
/reap --all --confirm # and actually do it
/reap 5173 # preview reclaiming one port
/reap 5173 3001 --confirm --force
How the scan works
| platform | endpoints | process table |
|---|---|---|
| Windows | Get-NetTCPConnection -State Listen (+ Get-NetUDPEndpoint) | Get-CimInstance Win32_Process |
| Windows (fallback) | netstat -ano | tasklist /FO CSV /NH |
| Linux | ss -H -ltnp | ps -eo pid=,ppid=,comm=,etimes=,args= |
| macOS | lsof -nP -iTCP -sTCP:LISTEN -Fpcn | ps |
Every probe is best-effort: a missing binary or a failed command degrades that one source and is reported in degraded: ... at the end of the scan. A scan that falls back to netstat still works — it just loses command lines, and therefore attribution gets weaker, which the report tells you.
One process listening on both :: and 0.0.0.0 is one problem, not two: dual-stack bindings are collapsed into a single row per protocol/port/pid, with every address kept in addresses.
Development
npm test # 42 unit tests, no harness needed
npm run test:live # a real read-only scan of this machine
npm run setup:deps
lib/ is plain ESM JavaScript with no build step, and the unit tests cover the parsers against recorded fixtures — Windows and BSD netstat tables, ss, lsof, ps, tasklist, the classifier, the plan builder, and the renderers. test:live covers what fixtures cannot: that the real enumeration works on the host you are on. It signals nothing.
setup:deps links the three host packages lib/ imports (cordis, dsh-tools, schemastery) into a local node_modules so a bare checkout can run the tests. Set DSH_HOST_NODE_MODULES if the harness is not in a standard location.
License
MIT
Comments
Loading…
Similar plugins
by davidgereb
Restart your ongoing agent sessions once on server restart in DeepSeek Harness.
★ 0
MIT
JavaScript
Aug 15, 2026
dsh plugin --profile web add dsh-plugin-watchdogby 1622352030
Session-stall watchdog for DeepSeek Harness: releases a hung turn in place instead of restarting the app.
★ 0
MIT
JavaScript
Sep 24, 2026
dsh plugin --profile web add dsh-session-rescueby flandre2233
Auto-resumes DSH turns interrupted by a host restart
★ 1
↓ 352/wk
MIT
JavaScript
Sep 24, 2026
dsh plugin --profile web add dsh-recovery-resumeby squirrel20
Unattended scheduled jobs for the DeepSeek Harness (dsh): agent/command tasks on cron schedules
★ 4
↓ 87/wk
MIT
JavaScript
Sep 21, 2026
dsh plugin --profile web add dsh-cronby wjackiedev
Archived-session manager for the DeepSeek Harness Web GUI — restore and permanently delete session logs.
★ 1
↓ 3.2k/wk
MIT
JavaScript
Sep 20, 2026
dsh plugin --profile web add dsh-session-managerby andrepontesmelo
DSH plugin: spawns one live session per ready Wayfinder task and draws the dependency map in the chat GUI
★ 0
MIT
JavaScript
Sep 29, 2026
dsh plugin --profile web add dsh-wayfinder-ui