Skip to content

examples/hooks: add snap_pack_on_stop.py for auto-pack on session end#55490

Open
achiii800 wants to merge 1 commit intoanthropics:mainfrom
achiii800:add-snap-pack-stop-hook-example
Open

examples/hooks: add snap_pack_on_stop.py for auto-pack on session end#55490
achiii800 wants to merge 1 commit intoanthropics:mainfrom
achiii800:add-snap-pack-stop-hook-example

Conversation

@achiii800
Copy link
Copy Markdown

Summary

Adds a robust opt-in Stop hook example that packs the current Claude Code session JSONL into a portable, lossless .snap.jsonl artifact when the session ends, then drops it at a configurable path ($CLAUDE_SNAP_DROP_PATH, default ~/Documents/claude-snaps).

The intended use case is hands-off cross-device session handoff — point $CLAUDE_SNAP_DROP_PATH at an iCloud Drive / Dropbox / Syncthing folder, and packed sessions auto-sync to other devices where they can be loaded into a Claude chat as continuation context.

This addresses one of the recurring requests in the cross-device-session cluster (#31992, #29847, #44063, #47926, #51816 — all variations of "let me move my session to another device"). It's not the official feature; it's a working example users can wire into their own settings today, while the official UX is being designed.

Why a hook example, not a CLI flag

The CLI flag conversation lives in #31992 ("Cross-machine session resume"). This PR doesn't try to add that — it adds the example users can wire up today using the existing hook system + an external codec.

Dependency

The hook calls into claude-snap (PyPI) — a ~600-LOC pure-stdlib lossless codec for Claude Code session JSONLs with byte-identical roundtrip. Distinct from existing tools in the space:

Tool Approach Lossless Byte-identical roundtrip Codec
claude-mem AI summarization no no no
cctrace md/xml + verbatim JSONL copy partial partial no (transcriber)
claude-conversation-extractor markdown export no no no
session-roam Syncthing dir sync yes trivially no (file sync)
claude-handoff git bundle with path scrubbing structurally no (paths rewritten) partial
claude-snap sha256 content-hash refs in JSONL yes yes yes

Soft import: if claude-snap isn't on PATH, the hook noops with a stderr message — Claude Code is never affected.

Robustness

  • Soft-fails when claude-snap is missing (no crashes)
  • Honors CLAUDE_SNAP_DISABLED=1 for ad-hoc disable without editing settings
  • Configurable destination via CLAUDE_SNAP_DROP_PATH env var
  • Uses subprocess.run with arg lists (no shell injection surface)
  • 60s timeout on the pack invocation
  • Smoke-tested for: happy path, missing binary, disabled flag, empty stdin

Test plan

  • Hook exits 0 and noops with stderr message when claude-snap is not on PATH
  • Hook exits 0 and produces <session-id>.snap.jsonl at $CLAUDE_SNAP_DROP_PATH when claude-snap is installed and a transcript path is provided
  • Hook exits 0 silently when CLAUDE_SNAP_DISABLED=1
  • Hook handles empty stdin (some Stop hook configs send no event JSON) by falling back to the most recent JSONL under ~/.claude/projects/
  • Subprocess timeout of 60s prevents the hook from hanging Claude Code on a stuck pack
  • No string interpolation into shells; all subprocess calls use arg lists

🤖 Generated with Claude Code

Adds a robust opt-in `Stop` hook example that packs the current Claude
Code session JSONL into a portable, lossless `.snap.jsonl` artifact
when the session ends, then drops it at a configurable path
(`$CLAUDE_SNAP_DROP_PATH`, default `~/Documents/claude-snaps`).

The intended use case is hands-off cross-device session handoff —
point `$CLAUDE_SNAP_DROP_PATH` at an iCloud Drive / Dropbox /
Syncthing folder, and packed sessions auto-sync to other devices
where they can be loaded into a Claude chat as continuation context.

The hook depends on the `claude-snap` codec
(https://github.com/achiii800/claude-snap, https://pypi.org/project/claude-snap/)
which is a ~600-LOC pure-stdlib lossless codec for Claude Code session
JSONLs with byte-identical roundtrip — distinct from existing tools
in the space (claude-mem summarizes, cctrace transcribes,
claude-conversation-extractor renders to markdown). It's the only one
that produces a single portable artifact that roundtrips byte-for-byte.

Robustness:
- Soft-fails if `claude-snap` isn't on PATH (never crashes Claude Code)
- Honors `CLAUDE_SNAP_DISABLED=1` for ad-hoc disable without editing
  settings
- Configurable destination via `CLAUDE_SNAP_DROP_PATH` env var
- Uses `subprocess.run` with arg lists (no shell injection surface)
- 60s timeout on the pack invocation
- Smoke-tested for happy path, missing-binary, disabled-flag, and
  empty-stdin cases

Wiring instructions are in the file's docstring; users opt in by
adding the hook entry to `~/.claude/settings.json`.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant