Bundle
dsh-session-recovery
Recover deleted/corrupted dsh sessions (session.jsonl.zstd) and memory (memory.db) straight from the raw disk. Battle-tested recovery manual + scripts + a /session-repair command plugin for the dsh web UI.
- Source
- Coprexist
- stars
- 2 stars
- License
- MIT
- Updated
- Updated 14 hours ago
Readme
<div align="center"> # dsh-session-recovery **Recover deleted/corrupted DeepSeek Harness sessions & memory from raw disk** [](https://github.com/deepseek-ai/deepseek-harness) [](https://nodejs.org) [](LICENSE) [](https://awesome-dsh-plugin.com) English · [中文](README.zh.md) </div> --- > 🛟 After `rm -rf ~/.dsh` or file corruption, dsh conversation logs (`session.jsonl.zstd`) and memory (`memory.db`) can usually be recovered **straight from the raw block device** — this repo is a battle-tested manual plus the scripts that made it work. ## ✨ Features | | | |---|---| | 🧠 **Memory recovery** | Locate `memory.db` by its `SQLite format 3` header, dump the window, rescue rows via SQLite's official `.recover` (skips corrupt pages, keeps readable data). | | 💬 **Session recovery** | Scan disk for zstd frame magic `0xFD2FB528`, cluster frames by disk offset, split sessions at `turn` resets, rebuild official-format `session.jsonl.zstd`. | | 🔧 **Automatic repair** | Renumber `seq` contiguously, deep-fix `sourceEventSeqs`/`messageSeqs`, normalize `surfaceOp` — the rebuilt log passes DSH's validation. | | 🔁 **Resume repair** | Rebuilt sessions can fail at *resume* (`invalid persisted inbox splice`, `Messages with role 'tool' must be a response to tool_calls`) — `repair-session.js` (or the `/session-repair` web command) replays DSH's inbox/surface/wire rules and fixes the file. | | 🛡️ **Safe by design** | Scripts only **read** the block device and write to a directory you choose; the original disk is never modified. | ## 🚀 Quick Start ```bash # 1. Stop everything that writes to disk (systemd services, dsh itself) systemctl stop dsh-web # 2. Recover memory (SQLite) node scripts/recover-memory.js /dev/<dev> /tmp/recovered/ # 3. Scan disk for session frames node scripts/scan-zstd.js /dev/<dev> > /tmp/session-events.jsonl # 4. Split mixed events into per-session files node scripts/split-sessions.js /tmp/session-events.jsonl 2026-08-16T07:05:06Z # 5. Rebuild a session file (official format) node scripts/rebuild-session.js \ --input /tmp/sess-A.jsonl \ --id session-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ --created-at 1786705157736 \ --cwd /path/to/workspace \ --out-dir ~/.dsh/sessions/--workspace-encoded--/ # 6. Before resuming: verify/repair the rebuilt session (fixes inbox splice # skew, duplicate/orphaned tool results, dangling tool calls) node scripts/repair-session.js \ ~/.dsh/sessions/--workspace-encoded--/<session-id>/session.jsonl.zstd --dry-run node scripts/repair-session.js \ ~/.dsh/sessions/--workspace-encoded--/<session-id>/session.jsonl.zstd # → writes session.jsonl.zstd.repaired (+ .bak-<ts>); review, then replace: cp ~/.dsh/sessions/--workspace-encoded--/<session-id>/session.jsonl.zstd.repaired \ ~/.dsh/sessions/--workspace-encoded--/<session-id>/session.jsonl.zstd ``` 📖 **Full step-by-step manual** (Chinese, with every DSH validation rule and error you may hit): **[RECOVERY.md](RECOVERY.md)** ## 📦 Install as a dsh plugin > **Note**: primarily a recovery toolkit (scripts + manual); it also installs as a dsh plugin that adds the **`/session-repair` command to the web UI**. From a directory containing this repo (local install, no publish needed): ```bash dsh plugin --profile web add file:/path/to/dsh-session-recovery systemctl restart dsh-web ``` Then type in **any** session in the web UI: ``` /session-repair --dry-run # analyze the current session (writes nothing) /session-repair <session-id-or-path> # repair → writes .repaired + a backup /session-repair --apply <id> # also replace the original file ``` By default only a new `.repaired` file plus a backup are written; `--apply` overwrites the original (refused for the live session hosting the command — run it from another session). Restart `dsh-web` after applying. Requires **Node 24+** (`node:sqlite`, `node:zlib`). ## 📖 Background DeepSeek Harness stores its data under `~/.dsh`: ``` ~/.dsh/ ├── sessions/<workspace-encoded>/<session-id>/session.jsonl.zstd # conversation log ├── memory/memory.db # dsh-mneme memory └── storages/workspace.json # workspace registry ``` The session log is a **concatenated stream of zstd frames — one frame per JSONL line** ([source](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence-jsonl)). That per-line framing is exactly what makes partial recovery possible: even if some frames are overwritten, the intact frames around them still decode independently. ## 🗂️ Repository Layout ``` ├── RECOVERY.md # Full recovery manual (Chinese, battle-tested) ├── cordis.patch.yml # dsh plugin patch (registers /session-repair) ├── docs/ │ ├── awesome-entry.yml # Ready-to-submit Awesome DSH Plugin list entry │ └── GITHUB-PUBLISH.md # Publish & PR checklist ├── lib/ │ ├── index.js # dsh plugin entry: /session-repair command │ └── repair.js # Shared repair core (CLI + plugin) ├── scripts/ │ ├── scan-zstd.js # Disk scan: find & cluster zstd session frames │ ├── split-sessions.js # Split mixed recovered events into per-session files │ ├── rebuild-session.js # Rebuild official-format session.jsonl.zstd │ ├── repair-session.js # Fix rebuilt sessions that fail to resume │ ├── make-test-fixture.js # Generate damaged samples to test repair offline │ └── recover-memory.js # Locate & rescue memory.db (SQLite .recover) └── package.json # dsh plugin manifest (dsh.bundle + main entry) ``` ## 🔖 Topics `dsh-plugin` · `dsh` · `deepseek-harness` · `session-recovery` · `data-recovery` · `zstd` · `sqlite` ## 📄 License [MIT](LICENSE)
Install
dsh plugin --profile web add github:Coprexist/dsh-session-recovery
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-session-recovery from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.