If you use Claude Code seriously, you’ve hit this wall: you’re deep in a session, SSH drops, and you come back to nothing. The session is gone. The context is gone. Start over.
That’s the problem I built
cs — Claude Session Manager to solve.
The Problem
Claude Code stores sessions as JSONL files in
~/.claude/projects/. Each file is a conversation. But there’s no built-in way to:
- Keep a session alive after your terminal closes
- See what’s running across multiple machines
- Reconnect to a session that’s running on a different host
If you work across a dev box, a staging server, and a local machine like I do, this gets painful fast.
How cs Works
cs wraps Claude sessions in detached tmux, syncs metadata to MongoDB, and adds a thin command layer on top.
Launch a persistent session:
cs launch myproject "fix the nginx config"
This starts Claude in a detached tmux session. You can close your terminal. Claude keeps running. When you come back:
cs attach myproject
If that session is on a different machine,
cs SSH’s there automatically and reconnects you. That’s the part I find genuinely useful — one command, any host.
The Dashboard
Running
cs with no arguments shows a merged view of all sessions across all synced machines:
cs
Sessions are color-coded by state:
- WORKING (green) — Claude is actively generating
- WAITING (yellow) — needs input or permission
- IDLE (dim) — alive but quiet
- DEAD (red) — tmux session is gone
Works even if MongoDB is down — falls back to local tmux state.
Cross-Machine Attach
This is the feature that made the whole thing worth building. Say you launched a session on your dev box earlier. You’re now on your laptop. Instead of SSH-ing manually, finding the tmux session name, and attaching:
cs attach a4b9
cs looks up that session ID prefix in MongoDB, determines it’s on
dev.example.com, SSH’s there using a persistent agent at
~/.ssh/cs-agent.sock, and drops you in. No agent forwarding config, no dotfile changes.
The session ID prefix works like git’s short SHA — you just need enough characters to be unambiguous.
Session Hygiene
A few commands I use regularly:
cs resume # fzf picker — select and jump to claude --resume
cs last # resume the most recent session, no questions asked
cs gc # prune /compact and /clear orphan sessions
cs prune --days 7 # soft-delete unnamed sessions older than 7 days
The
cs gc one is particularly useful. Every time you run
/compact in Claude Code, it creates a new session file with a system tag as the first message. These orphans pile up.
cs gc finds and removes them.
Architecture
The setup is intentionally minimal:
- Bun — single bundled JS binary, no Node modules to manage
- tmux — session persistence
- MongoDB — shared metadata layer (only metadata, not session content)
- SSH agent — managed per-machine at a fixed socket path
Session JSONL files stay local. Only metadata (title, host, timestamps, state) goes to MongoDB. The one-liner installer handles Bun if it’s missing, sets up cron sync every 5 minutes, and doesn’t touch
.bashrc.
Multi-Machine Setup
- Run the installer on each machine
- Point them all at the same MongoDB instance
- Set up SSH key auth between machines
That’s it. After the first
cs sync on each host, the dashboard shows everything.
Install
curl -sSL https://raw.githubusercontent.com/kshartman/claude-session/main/install-remote.sh | bash
Requires: Bun, tmux, MongoDB 5+ with TLS, Claude Code CLI. Optional: fzf for
cs resume.
Full docs and source at
github.com/kshartman/claude-session.
Caveats
A few things worth knowing before you build your workflow around this.
The session format is undocumented. Claude Code stores conversations as JSONL files in
~/.claude/projects/, one JSON object per line. The schema — message structure, field names, how
/rename labels are stored, what constitutes an “orphan” session — is internal to Claude Code and not publicly documented. I reverse-engineered it from the files on disk. Anthropic could change the format in any release and
cs sync would break silently or produce garbage metadata.
The /rename convention is inferred. When you run
/rename foo in Claude Code, it writes a specific marker into the session file.
cs detects this and uses it as the session name. This works today, but it depends on that marker staying stable.
State detection is heuristic. WORKING / WAITING / IDLE / DEAD status comes from reading tmux pane content and looking for patterns that suggest Claude is generating or waiting for input. It is not a real API — it is screen-scraping tmux. It is usually right, but not guaranteed.
MongoDB is a real dependency. This is not a lightweight setup. You need a running MongoDB instance with TLS, reachable from all your machines. If you do not already have one, that is a non-trivial prerequisite. I happen to run a personal MongoDB server already, so this was a non-issue for me — but I recognize it is the biggest barrier for anyone else picking this up.
MongoDB is also overkill for what
cs actually does. The queries are all simple key lookups and prefix matches — no joins, no aggregation pipelines. I have thought through what a simpler data driver would look like (SQLite via Bun’s built-in
bun:sqlite would be the right call for single-machine use; a plain JSON file would work too). The code is structured in a way that would make a pluggable driver layer straightforward to add. If there is enough interest from other people using this, that is probably the first thing I would tackle.
These are the trade-offs I made for my own use. The tool works well in practice, but if Anthropic ships a session format change, expect to pin your Claude Code version until the parser is updated.