Game-save your Claude Code sessions. Share them like files.
ccsave (CLI binary: claude-save) exports any Claude Code conversation — messages, tool outputs, the active plan, optionally project files — into a single portable .ccsave archive. Hand it to a teammate or another machine, and they can resume exactly where you left off. Like a game save for your Claude conversations.
┌────────────┐ export ┌─────────────────┐ ship ┌────────────┐
│ Sender │ ─────────────▶ │ session.ccsave │ ───────────▶ │ Receiver │
│ Claude │ │ (tar.gz) │ │ Claude │
│ Code │ │ │ │ Code │
└────────────┘ │ manifest │ └─────┬──────┘
│ session.jsonl │ │
│ tool-results/ │ │ /resume
│ summary.md │ │
│ plan.md │ ▼
│ snapshot/ ⋅⋅⋅ │ continue work
└─────────────────┘
- Single-file portable export — one
.ccsave(tar.gz) carries everything needed - Auto-redaction of common secrets: Anthropic / OpenAI / GitHub / AWS / Google / Slack keys, JWTs, PEM private keys,
.env-style values, generic high-entropy strings - Path tokenization — sender's
/Users/alice/work/repobecomes{{CWD}}in the archive, gets remapped to the receiver's working directory on import (cross-OS, cross-machine) - Three sharing channels
- Local file you send any way you like
- LAN HTTP server (
claude-save serve) — token auth + same-subnet check + rate limit - Push-style receive (
claude-save listen) — receiver opens an inbox endpoint
- Seamless one-shot import — receiver pastes the sender's URL once; CLI lists, picks, streams, and imports in a single command
- Claude-generated summary baked into each archive — receiver gets a structured digest (goal / decisions / progress / files / how-to-continue) and can resume without reading 200 messages
- Optional project-file snapshot — content-addressable, gitignore-aware, deduplicated
- Zero npm dependencies — Node ≥ 18 built-ins and system
tar - Slash command (
/share-session) on top of the CLI for use directly inside Claude Code
Clone into your Claude Code plugins directory:
git clone https://github.com/brave-orange/ccsave.git ~/.claude/plugins/claude-save
chmod +x ~/.claude/plugins/claude-save/bin/claude-save.jsOptionally symlink to your PATH:
ln -s ~/.claude/plugins/claude-save/bin/claude-save.js /usr/local/bin/claude-saveFor the slash command, create the skill file:
mkdir -p ~/.claude/skills/share-session
cp ~/.claude/plugins/claude-save/SKILL.md ~/.claude/skills/share-session/SKILL.mdOptional: brew install qrencode (macOS) or apt install qrencode (Linux) to render QR codes in serve mode.
# In any Claude Code session, after some real work:
claude-save export
# → ~/Downloads/session-<slug>-<short>.ccsave (242 KB typical)
# To share it on the LAN:
claude-save serve --port 8765
# → http://192.168.1.42:8765/?token=Xy7Q...kZ8# In a fresh Claude Code session, in the directory you want to work from:
cd /path/to/your/work/dir
claude-save import "http://192.168.1.42:8765/?token=Xy7Q...kZ8"
# → Picks newest session, streams archive, detokenizes paths, places JSONL
# → ✓ Imported as session 0817121c-...
# Then restart Claude Code in this directory and /resume to enter the session.That's it. Three commands across two machines.
/share-session export # in the sender's Claude Code
/share-session serve # start LAN server
/share-session import <url> # in the receiver's Claude Code
| Concept | What it means |
|---|---|
.ccsave |
tar.gz archive of one Claude session, with manifest + paths tokenized + secrets redacted |
| sender | The machine/user exporting the session |
| receiver | The machine/user importing it |
serve mode |
Sender exposes their archives over HTTP for pull |
listen mode |
Receiver exposes an inbox endpoint over HTTP for push |
| tokenization | {{CWD}} / {{HOME}} / {{CWD_ENCODED}} placeholders, remapped on import |
| snapshot | Optional content-addressable bundle of the sender's project files |
claude-save export [--session <uuid>] [--out <path>] [--include-snapshot]
[--no-redact] [--no-tokenize] [--no-summary]
[--summary-from <path>] [--gist] [--quiet]
claude-save import <archive-or-url-or-server-home> [--slug <s>]
[--target-cwd <path>] [--apply-snapshot] [--force]
[--dry-run] [--token <t>]
claude-save serve [--port 8765] [--bind 0.0.0.0] [--token auto|<t>]
[--dir <path>] [--accept] [--target-cwd <path>]
claude-save listen [--port 8766] [--target-cwd <path>] [--token auto|<t>]
(= serve --accept; receiver-side inbox)
claude-save fetch <peer-url> [--out <path>] [--token <t>] [--auto-import]
claude-save list [--limit 20] [--current] [--json]
claude-save inspect <archive> [--json]
| URL pattern | Behavior |
|---|---|
http://host:port/?token=xxx |
Server homepage → list sessions → pick newest → stream + import |
http://host:port/?token=xxx + --slug foo |
Same, but pick the session matching foo |
http://host:port/api/sessions/<slug>?token=xxx |
Direct archive download |
~/path/to/file.ccsave |
Local file |
# Receiver:
claude-save listen --port 8766 --target-cwd $(pwd)
# → POST http://<receiver>:8766/api/import?token=...
# Anyone (sender, automation, browser):
curl -X POST "http://receiver:8766/api/import?token=..." \
-H "Content-Type: application/octet-stream" \
--data-binary @session.ccsave
# Or have the receiver fetch from the sender's serve URL:
curl -X POST "http://receiver:8766/api/import?token=..." \
-H "Content-Type: application/json" \
-d '{"url":"http://sender:8765/api/sessions/foo?token=..."}'session-{slug}-{shortHash}.ccsave (tar.gz)
├── manifest.json metadata, redaction log, token map
├── summary.md Claude-generated digest
├── session.jsonl tokenized + redacted conversation
├── tool-results/ tool outputs (tokenized + redacted)
├── subagents/ child agent JSONL + meta
├── plan.md active plan (if any)
└── snapshot/ optional project files
├── files.json {relative-path: sha256, size, mode}
└── blobs/{sha[0:2]}/{sha[2:]} content-addressable, deduplicated
Inspect any archive without unpacking:
claude-save inspect ~/Downloads/session-foo-a8f2.ccsave
# Version: 1.0
# Session: 8761fe83-...
# Messages: 301 (user 85, assistant 125, tool uses 81)
# Redactions: anthropic-key×8, openai-key×4, github-pat×2, aws-key×2, ...
# One-line: Working on payment retry logic across services X and Y...- Auto-redaction is on by default. Every secret is replaced with
[REDACTED:type:#hash4]and recorded inmanifest.redactions.--no-redactrequires explicit flag and prints a warning. - HTTP server defaults to LAN-only. Requests from outside the host's subnet (or RFC1918 ranges) return 403, regardless of token.
- Token auth. 32-byte random token generated at
servestart; required as?token=query orAuthorization: Bearer. - Rate limited. 5 requests per IP per second.
- Archive defenses. Path-traversal entries rejected. Unpacked size capped at 500 MB to defeat zip-bombs.
- Imports are atomic. Everything is staged to a temp directory and only moved into
~/.claude/projects/after manifest + integrity validation pass. - Sender hostname leaks.
manifest.source.senderHostrecordsos.hostname(). Inspect before sharing if that's sensitive (flag to disable is on the v1.1 list).
| Item | Status |
|---|---|
| Claude Code 2.1.x | Tested |
| Node | ≥ 18 |
| Platforms | macOS, Linux, Windows (MSYS / Git Bash supported with auto path conversion) |
Project-dir encoding (/path → -path-) |
Observed empirically from Claude Code 2.1; falls back to scanning existing dirs on mismatch |
-
Export reads the session JSONL from
~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl, walks every string field, runs path tokenization and redaction, copies tool-results + active plan, optionally snapshots project files, thentar.gzs the staging dir. -
Tokenization uses regex with a
[\\/]slash class so the same archive works whether the receiver runs POSIX or Windows. Longest-first replacement avoids{{HOME}}swallowing a{{CWD}}prefix. -
Import unpacks into a temp dir, verifies
manifest.integrity.contentSha256, runs the inverse path mapping using the receiver'scwd/$HOME/ username, generates a freshsessionId(keeping internal message UUIDs intact so the parent chain stays valid), computes the receiver's encoded project dir, and atomically places the JSONL there. -
LAN server binds
0.0.0.0but performs a same-subnet check on every API call.serveis read-only;listen(a.k.a.serve --accept) exposesPOST /api/importthat either reads.ccsavebytes from the request body or fetches them from a peer URL given in a JSON body.
claude --resume <uuid>is not auto-invoked; restart Claude Code manually after import- Memory and tasks state are not yet bundled into the archive
cwd → projects/encoding rule is observed, not from public spec; future Claude Code releases may change it (discovery layer scans existing dirs to mitigate)- The HTTP server picks the first non-internal IPv4 interface, which may be a Hyper-V / VMware virtual one — pass the right
--bindif needed senderHostandclaudeCodeVersionfields in manifest are not yet opt-out
- v1.1: auto-resume hook · memory + tasks bundling · mDNS / Bonjour discovery for
listen· manifest signing · per-field redaction opt-out - v2.0: end-to-end encryption (receiver public key) · session diffing / patch archives · cross-session "branch from here"
node tests/run.js
# 29 passed, 0 failed (29 total)Covers tokenize edge cases, every redaction rule, CIDR / subnet logic, archive round-trip, full export-import round-trip across simulated cwds, and CLI smoke tests.
Issues and PRs welcome. Things especially helpful:
- Real-world false positives / negatives in the redaction rules
- Tested values for the
cwd → projects/encoding on Claude Code versions other than 2.1.x - Reports on which OS / shell / tar combination broke
--force-localpath conversion - Ideas for the v1.1 / v2.0 items above
MIT — see LICENSE.