DSH Plugin Store
Back to home

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
Other
GitHub repo

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/end with reason: event content passes DSH's content-level validation
  • Titles preserved: automatically takes the last ai-title from Claude records into a session/title event; 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=NEW fixes 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.py gates 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:

  1. workspace.create(path=cwd) — create if missing, reuse if present;
  2. session.create(sessionId, workspaceId) — mount via the resume path, which also folds the logged session/title event 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 web service 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:

  1. Multi-frame layout: session.jsonl.zstd must be a concatenation of independent zstd frames — frame 1 must decompress to exactly one session header line (newline-terminated). Single-frame compression makes DSH report corrupt Zstandard session log: first frame is not exactly one header line, which makes session.list fail entirely and the GUI session list go empty.
  2. turn/end reason: every turn/end event must carry a reason field (e.g. {"turn": 1, "reason": {"kind": "completed"}}), or DSH reports contains 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

SymptomCauseFix
Session list empty after refreshOne malformed session file makes session.list throwLocate with verify_session.py; regenerate with this converter; restart service after fixing
corrupt Zstandard session log: first frame is not exactly one header lineSingle-frame compressionRe-convert (multi-frame layout)
malformed pre-react-loop turn/end at seq Nturn/end missing reasonRe-convert (already fixed)
Sessions under "Ungrouped"Attach script not run / stale service statenode attach_to_workspace.mjs; restart service if needed
Title is workspace name or blankNo session/title eventSessions with no ai-title in source (command-only) keep no title, matching Claude
workspace ... ENOENTcwd directory renamed/deletedRe-convert with --cwd-map OLD=NEW

Files

FilePurpose
convert_claude_to_dsh.pyConverter: multi-frame zstd, turn/end reason, --cwd-map, title preservation
verify_session.pySession format/content validator
attach_to_workspace.mjsWorkspace attach script (reuse/create by cwd, restore title projections)
SKILL.mdSkill 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

License

MIT