Seafood-Y
CC2DSH
Import Claude Code session history into DeepSeek Harness (DSH): convert, verify, and attach conversations to workspaces with original titles restored.
- Stars
- 0
- Language
- Python
- Created
- Aug 14, 2026
- Updated
- Aug 14, 2026
Introduction
CC2DSH
English | 中文
Claude Code → DeepSeek Harness (DSH) session importer
Migrate your Claude Code conversation history (~/.claude/projects/<project>/<session>.jsonl)
into DeepSeek Harness session storage (~/.dsh/sessions/), so those conversations
become visible in the DSH Web GUI — grouped into the workspaces they originally
belonged to, with their original titles restored, and ready to continue chatting.
A skill/tool for the DeepSeek Harness ecosystem, built around
dsh's session persistence format.
Features
- ✅ Multi-frame zstd layout: generated session files strictly satisfy DSH's read requirements (frame 1 = exactly one header line)
- ✅
turn/endwithreason: event content passes DSH's content-level validation - ✅ Titles preserved: automatically takes the last
ai-titlefrom Claude records into asession/titleevent; the title projection is restored on attach - ✅ Workspace matching: reuses/creates workspaces by
cwd, sessions land in their original project directories - ✅ Renamed-directory handling:
--cwd-map OLD=NEWfixes projects that were renamed or moved after the session was recorded - ✅ Idempotent import: session ids match Claude's sessionId, so re-imports simply overwrite
- ✅ Format validator:
verify_session.pygates imports so one bad file can never take down the whole session list
Quick start
# 1. Convert all sessions (default output: ~/.dsh/sessions)
python3 convert_claude_to_dsh.py
# 2. Validate format and content
python3 verify_session.py
# 3. Attach to workspaces (titles are restored too; if the dsh web service was
# started before the import, restart it first so memory matches disk)
node attach_to_workspace.mjs
Then refresh the DSH Web GUI (default http://127.0.0.1:12788) — sessions appear
grouped by workspace, titled with the original Claude titles.
Usage
Convert (Claude JSONL → DSH session files)
# All sessions
python3 convert_claude_to_dsh.py
# Specific files, output to a test directory
python3 convert_claude_to_dsh.py a.jsonl b.jsonl --out /tmp/cc2dsh-test
# Fix renamed directories (example)
python3 convert_claude_to_dsh.py --cwd-map "/old/path=/new/path"
# Preview without writing
python3 convert_claude_to_dsh.py --dry-run
# List all importable Claude sessions
python3 convert_claude_to_dsh.py --list
Validate (format + content)
python3 verify_session.py # validate all of ~/.dsh/sessions
python3 verify_session.py /tmp/cc2dsh-test # validate any directory
Checks: ≥2 frames, frame 1 is exactly one header line, complete event lines,
every turn/end carries reason.kind, header fields present; also prints each
session's title for manual review. Exit code 0 = all good.
Attach to workspaces
node attach_to_workspace.mjs # attach all unattached sessions
node attach_to_workspace.mjs --dry-run # preview
For each session, the script uses the cwd from its header to:
workspace.create(path=cwd)— create if missing, reuse if present;session.create(sessionId, workspaceId)— mount via the resume path, which also folds the loggedsession/titleevent into the title projection.
Note: attaching uses the resume path, so sessions become live agents. If the DSH service has been running since before the import (stale in-memory state), restart the
dsh webservice first, then run this script.
Why this tool exists (background)
DSH session files have strict format requirements, and naively compressing the
whole JSONL with single-frame zstd breaks them:
- Multi-frame layout:
session.jsonl.zstdmust be a concatenation of independent zstd frames — frame 1 must decompress to exactly one session header line (newline-terminated). Single-frame compression makes DSH reportcorrupt Zstandard session log: first frame is not exactly one header line, which makessession.listfail entirely and the GUI session list go empty. turn/endreason: everyturn/endevent must carry areasonfield (e.g.{"turn": 1, "reason": {"kind": "completed"}}), or DSH reportscontains malformed pre-react-loop turn/end.
The converter here generates multi-frame files directly per the DSH persistence
spec (dsh-session-persistence-jsonl), avoiding both failure modes at the source.
Renamed / moved directories
The cwd recorded by Claude Code is the project path at the time. If the
directory was later renamed or moved, DSH workspace matching by realpath fails
(workspace ... ENOENT). Fix it with --cwd-map:
python3 convert_claude_to_dsh.py \
--cwd-map "/old/path=/new/path" \
--cwd-map "/another/old=/another/new"
Converted sessions are then written to the new path's project directory, and
the header cwd is updated accordingly.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Session list empty after refresh | One malformed session file makes session.list throw | Locate with verify_session.py; regenerate with this converter; restart service after fixing |
corrupt Zstandard session log: first frame is not exactly one header line | Single-frame compression | Re-convert (multi-frame layout) |
malformed pre-react-loop turn/end at seq N | turn/end missing reason | Re-convert (already fixed) |
| Sessions under "Ungrouped" | Attach script not run / stale service state | node attach_to_workspace.mjs; restart service if needed |
| Title is workspace name or blank | No session/title event | Sessions with no ai-title in source (command-only) keep no title, matching Claude |
workspace ... ENOENT | cwd directory renamed/deleted | Re-convert with --cwd-map OLD=NEW |
Files
| File | Purpose |
|---|---|
convert_claude_to_dsh.py | Converter: multi-frame zstd, turn/end reason, --cwd-map, title preservation |
verify_session.py | Session format/content validator |
attach_to_workspace.mjs | Workspace attach script (reuse/create by cwd, restore title projections) |
SKILL.md | Skill documentation (full workflow + troubleshooting) |
testdata/ | Session backups for regression testing |
Testing
The repo ships two real session backups (testdata/) for regression testing:
python3 convert_claude_to_dsh.py <path-to-jsonl> --out /tmp/cc2dsh-test
python3 verify_session.py /tmp/cc2dsh-test
The full end-to-end flow (delete → restart to take effect → re-import → attach → titles restored) has been verified against real sessions.
Related
- DeepSeek Harness — official repo
- dsh-plugin topic — DSH ecosystem discoverability
- DeepSeek Harness Discussions — community