rejoin lets you jump back into any session of Claude Code, Codex, OpenCode, Pi, OpenClaw, and Hermes — and gives you one dashboard of ALL your agentic sessions.
Using more than one agent harness means your work gets scattered. Claude
Code stores sessions in ~/.claude, Codex in ~/.codex, Hermes in a
SQLite DB, OpenClaw in its own nested tree — and each ships a picker that
only sees its own.
When you want to pick up where you left off, you first have to remember which harness you were in. "Was that webhook debugging in Claude or Codex?" turns into a scavenger hunt across six different session stores.
rejoin is the single pane of glass. One keyboard-first dashboard — web or terminal — that:
- Threads every agent's sessions into one searchable view.
Auto-titles each one into a scannable headline ("HTTP Client Query String
Redaction", not
e0a57d18-…); FTS5 search across the lot. - Pin Thread keeps the sessions you're actively working on at the top. Star a thread once and it floats above everything else, across every filter and view.
- Jump back in with one keystroke. rejoin spawns the agent's own
resume command (
claude --resume,codex resume,hermes --resume, etc.) inside tmux when present — or hands you the exact command to paste into any terminal you prefer.
pipx install rejoin
rejoin # web UI at http://127.0.0.1:8767
rejoin-tui # terminal UI (tmux-aware)Or pip install rejoin if you're not using pipx.
Full install with API-key setup → Install. Walkthrough → docs/TUTORIAL.md.
A warm-beige browser dashboard borrowed from Claude.ai's visual identity. Scan sessions in a two-pane layout, read the transcript in typographic serif/sans, click rejoin in tmux to pick back up where you left off. (Hero shot above.)
The same dashboard as a first-class TUI. Inside tmux, Enter opens a new window with the agent already loaded — no browser, no attach command, zero friction. Outside tmux it falls back to a detached tmux session you can attach from anywhere, or you can copy the raw resume command and run it in whatever terminal you prefer.
| ✅ Indexes every agent harness in one place | ❌ Not tied to any one harness |
| ✅ Reads your local session files | ❌ Never writes to them |
| ✅ Cross-agent search, group-by-cwd, Pin Thread | ❌ No cloud sync |
| ✅ One-keystroke jump-back-in (tmux or any terminal) | ❌ No auth / multi-user |
| ✅ Web + terminal UIs | ❌ Not a replacement for claude -r / codex resume — a superset |
| ✅ MIT, pure Python, small deps | ❌ Not a framework |
- Six harnesses: Claude Code, Codex, OpenClaw, and Hermes via our own parsers (Hermes contributes its native titles); OpenCode and Pi via
agent-sessions. - Jump back in, anywhere: rejoin runs
claude --resume,codex resume,hermes --resume, or the agent's equivalent — inside tmux when you have it, or as a paste-able command for any terminal. - Pin Thread: ★ a session and it stays at the top across every filter, search, and view. Use it for the 2–3 active threads you keep returning to.
- Auto-titled headlines:
qwen/qwen3-30b-a3b-instruct-2507writes a 3-6 word title for each session (~$7e-6 each). Falls back to the first prompt if no API key. - Search (FTS5 with hit highlighting), group-by-project, active-session pulse (
ps aux+ mtime). - Incremental reindex every 60 s. Reads only — never writes to your session files.
- Keyboard-first:
j/k/↑/↓navigate,Enterjumps in,ppins,/focuses search.
Verify each of these returns a success exit code before continuing.
python3 --version # must print 3.11 or higher
git --version # any version
tmux -V # any version; required only for rejoin clicksIf python3 is older than 3.11:
- Ubuntu/Debian:
sudo apt install python3.12 python3.12-venv - macOS (Homebrew):
brew install python@3.12
If tmux is missing:
- Ubuntu/Debian:
sudo apt install tmux - macOS:
brew install tmux
git clone https://github.com/akakabrian/rejoin.git ~/AI/tools/rejoin
cd ~/AI/tools/rejoinThe target path is arbitrary; ~/AI/tools/rejoin is an example.
python3 -m venv .venv
.venv/bin/pip install --upgrade pip
.venv/bin/pip install -e '.[dev]'Verify:
.venv/bin/python -c "from rejoin.app import main; from rejoin.tui import main as tmain; print('ok')"
# expected output: okWithout a key, sessions get a fallback title (truncated first prompt). With a key, they get a ~5-word LLM-generated title for about $7×10⁻⁶ each.
Pick one of three methods, in priority order:
Method A — shell env (most portable):
export OPENROUTER_API_KEY="sk-or-v1-…"Method B — project-local .env (default for agents):
echo 'OPENROUTER_API_KEY=sk-or-v1-…' > .env
chmod 600 .envThis file is gitignored. It's the method most automated setups should use.
Method C — point at an existing .env:
export OPENROUTER_ENV_FILE="/path/to/existing/.env"Verify:
.venv/bin/python -c "from rejoin.config import openrouter_api_key; print('key:', 'yes' if openrouter_api_key() else 'no')"
# expected: key: yes (or 'no' if you skipped this step)./run.shExpected output on stdout:
INFO: Started server process [NNNN]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8767
Open http://127.0.0.1:8767/ in a browser. Or launch as a Chrome app window:
google-chrome --app=http://127.0.0.1:8767/ --user-data-dir=/tmp/chrome-rejoin &curl -s http://127.0.0.1:8767/status
# expected: {"last_indexed_age_s": <small number>}
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8767/
# expected: 200To use the terminal UI instead:
./run-tui.shCreate ~/.config/rejoin/config.toml to override defaults. Every key is optional; see config.example.toml for the full annotated list.
# ~/.config/rejoin/config.toml
host = "127.0.0.1" # "0.0.0.0" ONLY on trusted networks
port = 8767
model = "qwen/qwen3-30b-a3b-instruct-2507"
transcript_tail = 40
active_window_sec = 120
long_turn_lines = 30
long_turn_chars = 1500
refresh_interval_sec = 60
title_concurrency = 8
turn_cache_size = 16Bad TOML prints a warning to stderr and falls back to defaults. Missing file → all defaults.
| key | action |
|---|---|
j / ↓ |
next session |
k / ↑ |
previous session |
g |
jump to top |
Enter |
rejoin the selected session in tmux |
p |
pin / unpin the open session |
/ |
focus search |
Esc |
blur / clear search |
Click the amber ★ on any row to pin/unpin without opening the session. Click ↻ in the header to force a reindex; the indexed Ns ago label confirms the background loop is alive.
| path | purpose |
|---|---|
~/.local/share/rejoin/index.db |
SQLite cache (sessions, titles, pins, FTS5) — safe to delete |
~/.config/rejoin/config.toml |
your overrides (optional) |
<project>/.env |
project-local OpenRouter key (optional, gitignored) |
~/.claude/projects/**/*.jsonl |
Claude Code source (read-only) |
~/.codex/sessions/**/*.jsonl |
Codex source (read-only) |
~/.local/share/opencode/opencode.db |
OpenCode source (read-only) |
~/.pi/agent/sessions/**/*.jsonl |
Pi source (read-only) |
~/.openclaw/agents/**/*.jsonl |
OpenClaw source (read-only) |
~/.hermes/state.db |
Hermes source (read-only) |
rejoin never writes to session files. It only reads. The SQLite index is a pure cache; deleting it forces a clean reindex on next launch (titles re-generate).
The dashboard exposes your full transcript history — every prompt, every tool call, every response. Treat it as sensitive:
- Default bind is
127.0.0.1. The server is reachable only from the same machine. - Only set
host = "0.0.0.0"on machines where you trust every peer on the network (e.g. a Tailnet-only host). There is no authentication. - Transcripts may include pasted secrets. Deleting a session file in
~/.claudeor~/.codexleaves the data in rejoin's SQLite cache until the next reindex-with-delete. If you want to purge: stop the server, delete~/.local/share/rejoin/index.db, restart.
rejoin/
├── common.py # Tool Literal, iter_jsonl, text_of, utcnow_iso, short_cwd
├── config.py # tomllib loader + defaults
├── db.py # SQLite schema (sessions, titles, pins, session_fts); schema-version guard
├── indexer.py # Claude + Codex parsers; PARSERS registry; OpenCode+Pi via external.py
├── titler.py # async OpenRouter batch; concurrency cap; content-hash skip
├── transcript.py # turn extraction (claude, codex); opencode+pi delegate to external.py
├── resume.py # `cd <cwd> && <tool> --resume <id>`; tmux launch; missing-binary check
├── external.py # adapter for Lars de Ridder's agent-sessions library
├── app.py # FastAPI routes, background refresh loop, HTMX templates
├── tui.py # Textual TUI app, tmux-aware rejoin
├── tui.tcss # TUI stylesheet
├── templates/ # HTMX-rendered HTML fragments
└── static/ # web CSS + JS
| symptom | fix |
|---|---|
'tmux' not found on PATH in the resume response |
install tmux (sudo apt install tmux or brew install tmux) |
| port 8767 already in use | set port = <other> in config, or REJOIN_PORT=<other> ./run.sh |
| titles stuck at truncated first prompt | OPENROUTER_API_KEY not found; see step 4 above |
| dashboard is empty | you have no Claude/Codex/OpenCode/Pi sessions yet; run one and click ↻ |
Schema version mismatch on startup |
back up and delete ~/.local/share/rejoin/index.db, restart |
| TUI shows no transcript on first launch | click any row; the first-paint transcript load races with the cursor init |
.venv/bin/pytest -q
.venv/bin/ruff check rejoin tests- Visual identity inspired by Claude.ai — the warm Pampas/Crail beige palette is an homage to Anthropic's Styrene + Tiempos design system, implemented here with free Fraunces / DM Sans / Source Serif 4 / IBM Plex Mono.
- OpenCode + Pi providers and running-process detection come from
agent-sessionsby Lars de Ridder (MIT). - Textual by Textualize powers the TUI.
MIT. See LICENSE.

