Three paired Claude Code skills + hooks that organize and round-trip session context across sessions, machines, and LLM providers.
conversation-summary— at session end, aSessionEndhook spawns a headlessclaude --print --barechild that reads the transcript and writes.context-handoff.jsonat the git repo root of the cwd (falling back to cwd if not in a repo). The summary is a structuredcontext-handoff-merged-v3snapshot. When the file lands inside a clean git repo (no rebase/merge in progress, not detached HEAD, not gitignored), the hook also auto-commits it on the current branch aschore: update claude session handoffso the handoff travels with the project across machines via your normalgit push/git pull. To opt out per-repo, add.context-handoff.jsonto that repo's.gitignore.load-context— in a new session, invoke/load-context(or say "load context", "load the handoff", etc.). The skill reads.context-handoff.jsonfrom the git repo root (falling back to cwd), validates the schema, orients to the prior state, and waits for the user's next instruction. Loading is always explicit — there is no auto-load on session start.resume-session— a name registry mapping human-friendly names (e.g.my-feature-work) to Claude Code session IDs so prior conversations can be resumed by name instead of UUID. AUserPromptSubmithook catches/save <name>, aSessionEndhook auto-registers any session that wasn't manually named, andcc-resume <name>(optional shell wrapper) prints/runs the exactcd ... && claude --resume <id>command. Subprocess Claude invocations that setCLAUDE_NO_AUTO_REGISTER=1in their env are skipped so pipeline workers don't pollute the registry.
- Resume a long task in a new Claude Code session without re-explaining context.
- Hand off a session between Claude models (Opus → Sonnet) or between Claude and a different provider.
- Resume an old session by a human-friendly name months later instead of hunting for a UUID.
- Search prior conversations by topic, name, or content via
session_registry.py search. - Feed
.context-handoff.jsoninto other automation that needs to know what a session did without parsing the raw transcript.
- Claude Code (
claudeCLI onPATH) jq,python3,bashgit(for resolving the git repo root when writing the handoff)- Linux/macOS (paths assume
$HOME/.claude/; not tested on Windows)
git clone git@github.com:pwnpanda/claude-session-organizer.git ~/git/priv/claude-session-organizer
cd ~/git/priv/claude-session-organizer
./install.shinstall.sh is idempotent. It:
- Symlinks
~/.claude/skills/{conversation-summary,load-context,resume-session}→ the corresponding subdirectories in this repo. - Symlinks
~/.claude/commands/save.md→<repo>/resume-session/commands/save.md. - Merges three hooks into
~/.claude/settings.json:SessionEnd→write_summary.sh,SessionEnd→session_end_hook.py,UserPromptSubmit→rename_hook.py. Existing hooks are preserved. - Purges any legacy
SessionStartauto-load entry left over from older versions.
After install, restart Claude Code (or open /hooks once) so the file watcher picks up the new hooks.
Optional: add the cc-resume shell wrapper to .zshrc/.bashrc so you can restore by name from any shell:
source "$HOME/.claude/skills/resume-session/shell/cc-resume.sh"Optional: add the airesume wrapper to resume a session across all three agents (claude/codex/gemini) by typing only a name prefix. Source it after your claude/codex/gemini aliases so each picked session launches with your normal per-agent flags:
source "$HOME/.claude/skills/resume-session/shell/airesume.sh"airesume <prefix> scans every registry, prefers a match in the current folder, shows an arrow-key picker when several match, and cds into the chosen session's folder before launching. airesume -s '<text>' resolves by free-text description instead — keywords scored against name, summary, cwd, then transcript bodies. Restrict the scan to one agent with a leading -c (claude), -g (gemini), or -x (codex). airesume with no prefix lists recent named sessions per agent. install.sh offers to append the source line to ~/.zsh_alias for you.
To uninstall:
./uninstall.shRemoves the symlinks (only if they still point at this repo) and removes the hook entries. Leaves .context-handoff.json files, the session-name registry at ~/.claude/session-names/, and any .bak.* directories alone.
/save my-feature-work # in a Claude Code session, registers it by name
Later, from any shell:
cc-resume my-feature-work # if you sourced cc-resume.sh
# or manually:
python3 ~/.claude/skills/resume-session/scripts/session_registry.py resume-cmd my-feature-work
# → prints: cd <cwd> && claude --resume <session-id>- Work in Claude Code as normal inside a project.
- Exit the session (
/quit,Ctrl+C, etc.). - The
SessionEndhook fires asynchronously and writes<repo-root>/.context-handoff.json. - Start a new Claude Code session anywhere inside the same repo.
- Tell Claude to load the context:
/load-contextor "load the handoff" — it reads the file, validates the schema, and acknowledges the prior state briefly. - Give the next instruction. The agent now has the prior context loaded and waits for direction.
Invoke /conversation-summary to produce a fresh .context-handoff.json without ending the session.
Copy .context-handoff.json and paste its contents into another LLM's chat with the instruction "load this prior-session context", and continue.
tail -f ~/.claude/skills/conversation-summary/last-run.logThe log records writer runs (success, failure, skipped trivial sessions). The session registry lives at ~/.claude/session-names/index.json and is updated atomically.
Pipelines that spawn claude as a subprocess (e.g. SAST scanners, batch summarizers) can flood the session-name registry with one auto-registered entry per worker run. To suppress that, set CLAUDE_NO_AUTO_REGISTER=1 in the subprocess's env:
env = os.environ.copy()
env["CLAUDE_NO_AUTO_REGISTER"] = "1"
subprocess.run(["claude", "-p", prompt], env=env, check=True)The SessionEnd hook honors the env var: it still refreshes registry entries for sessions that are already manually registered, but skips the "auto-register if not already registered" path. Manually named sessions are never affected. The --bare flag on claude --print --bare already skips hooks in the child entirely, so this env var is defense-in-depth for callers that don't (or can't) use --bare.
No automated tests — these are small shell + Python scripts wired to Claude Code lifecycle hooks. Verification is manual:
- Writer pipe test: feed a synthetic SessionEnd payload to
write_summary.shwith a stubclaudebinary; check that.context-handoff.jsonlands at the git repo root and is valid JSON. - End-to-end handoff: run a real session, exit, restart in the same repo,
/load-context, verify it reads and acknowledges. - Registry:
/save name, exit,python3 session_registry.py resume-cmd namefrom a fresh shell.
shellcheck runs cleanly on every script. Run it before committing:
shellcheck conversation-summary/scripts/*.sh install.sh uninstall.sh resume-session/shell/*.sh.
├── conversation-summary/ # write-side: SessionEnd hook + skill prompt
│ ├── SKILL.md
│ └── scripts/write_summary.sh
├── load-context/ # read-side: /load-context skill prompt
│ └── SKILL.md
├── resume-session/ # name registry + slash commands + shell wrapper
│ ├── SKILL.md
│ ├── README.md
│ ├── commands/save.md # /save slash command
│ ├── scripts/
│ │ ├── session_registry.py # registry CLI (resume-cmd, search, list, …)
│ │ ├── session_end_hook.py # SessionEnd hook (registers / touches)
│ │ └── rename_hook.py # UserPromptSubmit hook (/save <name>)
│ ├── shell/cc-resume.sh # optional shell wrapper
│ └── references/setup.md
├── install.sh
├── uninstall.sh
├── README.md
└── .gitignore
--bareis critical for the writer. Without it, the childclaudewould inherit the sameSessionEndhook and recurse.--bareskips hooks in the child.- Async + disowned writer. The writer backgrounds +
disowns itsclaudeinvocation so it survives the parent session's exit. - Repo-root output.
.context-handoff.jsonlives at the git repo root so the summary travels with the project, not the subdirectory the session happened to start in. The loader checks repo root first, then falls back to cwd. - Standardized filename.
.context-handoff.json(hidden, schema-named) avoids collisions with project files. - Schema gating on load. The loader only ingests files whose
template_idmatches a known prefix. - Manual-only load. Earlier iterations auto-injected the handoff via a
SessionStarthook; that was removed in favor of explicit user invocation. - Descriptive, not prescriptive. After
/load-context, the agent acknowledges and waits — never starts working onunfinished_workitems. - Atomic writes. A failed
claudeinvocation leaves no partial file; the writer only renames.tmp→ final afterjqconfirms valid JSON. - Registry hygiene. Subprocess Claude invocations that set
CLAUDE_NO_AUTO_REGISTER=1are skipped by the SessionEnd hook so pipeline workers don't pollute the registry.
| Session | Summary | Date |
|---|---|---|
custom-resume |
Added airesume: a prefix-resume subcommand in session_registry.py plus a shell/airesume.sh function that resumes a named session across Claude/Codex/Gemini by name-prefix — preferring a match in the current folder, falling back to an exact/unique match elsewhere, and showing an arrow-key picker when several match. -c/-g/-x restrict the scan to one agent; -s '<text>' resolves by free-text description (keyword scoring over metadata, then transcript bodies); install.sh offers to wire the function into ~/.zsh_alias. |
2026-05-20 |