Claude Code keeps every session as a JSONL file under
~/.claude/projects/<encoded-cwd>/<uuid>.jsonl. That means your history is
bound to the folder it was created in, keyed only by an opaque UUID, and
searchable only by grepping raw files.
csess mirrors that history into a local Postgres database so you can:
- 🔎 Search the full text of every conversation — not just the first line
- 🏷️ Tag sessions and browse by topic
- 🤖 Auto-title + auto-tag sessions with a cheap Haiku call
- 🎯 Fuzzy-find a session (
fzf) and resume it in any folder — the folder binding becomes a client-side detail - 🔁 Auto-sync every turn via a Claude Code hook (incremental — only new bytes are shipped, since JSONL is append-only)
Everything runs locally. The database is bound to 127.0.0.1 only.
~/.claude/projects/**/*.jsonl Postgres (Docker, localhost)
┌───────────────────────────┐ index/push ┌────────────────────────────┐
│ session JSONL (per folder) │ ─────────────▶ │ metadata · tags · FTS │
└───────────────────────────┘ │ full JSONL body (bytea) │
▲ claude --resume └────────────────────────────┘
│ │ load
┌─────────┴───────────┐ ▼
│ any folder, resumed │ ◀─────────────── materialize session into cwd
└─────────────────────┘
install.sh auto-installs everything else for you via Homebrew — Docker
(headless, via Colima), fzf, and python3 — and starts the Docker daemon. So
you only need two things up front:
| Need | Why |
|---|---|
| Homebrew | the installer uses it to fetch the rest |
| Claude Code | provides the sessions (claude on your PATH) |
Everything below is handled automatically by the installer:
| Auto-installed | Purpose |
|---|---|
| Docker (Colima) | hosts the Postgres database, started headlessly |
fzf |
powers csess find |
python3 |
runs the csess script |
Already have Docker Desktop? The installer detects it and just starts it instead of installing Colima.
git clone https://github.com/yexela/csess.git
cd csess
./install.shOn a fresh install this:
- starts Postgres and applies the schema;
- links
csessinto~/.local/bin(adding it to yourPATHif needed); - indexes every session already in
~/.claude— your whole existing history, retroactively, not just sessions created from now on; - offers to generate AI titles + tags for those existing sessions right then
(a
[y/N]prompt — say yes for a browsable history out of the box, or skip it and runcsess summarizewhenever). Auto-skipped if it's non-interactive orclaudeisn't on your PATH; - prints a summary and the optional auto-sync hook to add.
install.sh is idempotent — safe to re-run anytime (e.g. after git pull).
New here? Just run csess (or csess start) — it opens an interactive menu
of every action, so you don't have to memorize commands:
csess> ▏ type to filter · ↑↓ move · Enter select · Esc quit
❯ 🔎 find & resume a session
📋 list recent sessions
🔍 search sessions by text or tag
🏷 add tags to a session
🤖 generate AI titles + tags
⬆ back up session bodies
🔄 re-scan sessions into the DB
Or call the commands directly:
csess find # interactive fuzzy picker → Enter resumes here
csess search testflight # full-content search (matches anywhere in a convo)
csess list # recent sessions with titles + tags
csess summarize # AI title + auto-tags for all untitled sessions (also offered at install)
csess tag <uuid> billing # your own tags (UUID prefix is enough)
csess run <uuid> # materialize into current folder + claude --resume
csess index # re-scan metadata (also run by the hook)
csess push # store/refresh JSONL bodies (incremental)csess load/run copy the session's JSONL into the current folder's
project dir (preferring the body stored in Postgres, falling back to the
original file) and then call claude --resume. So a session created in project
A can be resumed from project B — or, once the DB lives on a server, from
another machine entirely.
csess update # git-pulls the latest, tells you the new versionOr manually:
cd /path/to/csess && git pullThe csess command is a symlink into the clone, so a pull takes effect
immediately. If a release changes dependencies or the database schema, re-run
./install.sh (it's idempotent). Check what you're on with csess version, and
see CHANGELOG.md for what changed.
- Metadata + FTS —
csess indexparses each JSONL for cwd, branch, timestamps, message count, the first user message, and all conversational text. A Postgres generatedtsvectorover (summary + first message + content) powerscsess search. - Bodies —
csess pushstores the raw JSONL in abyteacolumn, incrementally: it tracks how many bytes are already stored and appends only the new tail each time (JSONL is append-only). This is what makes a session portable independent of~/.claude. - Auto-sync hook —
csess hookreads thetranscript_pathfrom the hook payload on stdin and indexes + pushes only the current session. Wired toStop(every turn) andSessionEnd, so the DB tracks live sessions.
Environment variables (all optional):
| Var | Default | Purpose |
|---|---|---|
CSESS_DSN |
(unset) | Postgres connection string for a remote DB (see below); overrides the local container |
CSESS_CONTAINER |
claude-sessions-db |
Postgres container name |
CSESS_DB |
sessions |
database name |
CSESS_DB_USER |
postgres |
database user |
CSESS_WORKDIR |
~/.cache/csess |
scratch dir for internal claude -p calls |
CLAUDE_PROJECTS_DIR |
~/.claude/projects |
where Claude Code stores sessions |
By default csess talks to the local Docker container — no host psql client
needed. To point it at a remote Postgres instead (e.g. a shared server so
several machines share one history), set a connection string:
export CSESS_DSN="postgresql://user:pass@db.example.com:5432/sessions"
csess index # now reads/writes the remote DB- With
CSESS_DSNset, csess uses a hostpsqlif present, otherwise routes through the local container as a client. - Apply
schema.sqlto the remote database once before first use. - Everything else is identical — indexing your local sessions and resuming them into folders works the same; only the storage moves off-box.
- Unset
CSESS_DSNto switch back to local.
- The database contains your full session transcripts, which can include
secrets, code, and file contents. The container binds to
127.0.0.1only. - Nothing is uploaded anywhere. Everything stays on your machine.
- Lift Postgres off-box (RDS / self-hosted) for multi-machine shared history
- Cross-machine
pull - Optional MCP tool so you can search sessions from inside a conversation
csess: command not found — make sure ~/.local/bin is on your PATH.
psql error: ... connection refused — the database isn't running. Locally:
docker compose up -d (or docker start claude-sessions-db).
csess find just prints a list — install fzf: brew install fzf.
Search returns nothing — run csess index (metadata); AI titles need
csess summarize.
Auto-sync hook isn't firing — hooks load at session start, so they only
affect sessions started after you added them. Use the absolute path to
csess in ~/.claude/settings.json (hooks may run without ~/.local/bin on
PATH), and confirm the container is running.
load: "no stored body and original file missing" — that session's body was
never pushed and the original file isn't on this machine. Run csess push on the
machine that has it.
MIT © Oleksii Chernetskyi
