Closed
Conversation
External community plugin sourced from wonbywondev/claude-plugins via git-subdir. Preserves Claude Code session history across project directory renames, moves, and copies by assigning each project a path-independent UUID and maintaining a global registry mapping UUID to current path. Closes anthropics/claude-code#5768 Related: anthropics/claude-code#39148 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Thanks for your interest! This repo only accepts contributions from Anthropic team members. If you'd like to submit a plugin to the marketplace, please submit your plugin here. |
Open
25 tasks
Author
|
Follow-up: submitted via the official plugin directory form (https://claude.ai/settings/plugins/submit) per the bot's guidance above. Leaving this PR as a public reference for the proposed |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
Adds
preserve-sessiontoclaude-plugins-officialas an external community plugin, sourced fromwonbywondev/claude-pluginsatplugins/preserve-session/.Marketplace entry (added to
.claude-plugin/marketplace.json):{ "name": "preserve-session", "description": "Preserves Claude Code session history across project directory renames, moves, and copies by assigning each project a path-independent UUID", "version": "1.1.1", "author": { "name": "won", "email": "wonbywondev@gmail.com" }, "homepage": "https://github.com/wonbywondev/claude-plugins/tree/main/plugins/preserve-session", "source": { "source": "git-subdir", "url": "wonbywondev/claude-plugins", "path": "plugins/preserve-session", "ref": "main" }, "category": "productivity" }Related:
claude-code. After investigating contribution conventions, this marketplace turned out to be the more appropriate venue for an external community plugin. Both PRs are intentionally kept open until merge direction is confirmed.Demo
demo.mov
Summary
preserve-sessionsolves a persistent Claude Code limitation: session history becomes unreachable when you rename, move, or copy a project directory. The plugin assigns each project a path-independent UUID stored in.claude/hash.txt, maintains a global registry mapping UUID → current path, and provides commands to recover and reorganize session history when paths change.Commands:
/preserve-session:fix— recover sessions after a rename or move; detect and handle copies independently/preserve-session:inherit— copy session history from another registered project/preserve-session:doctor— diagnose the current project's session state and registry health/preserve-session:uninstall— permanently remove all preserve-session data (registry and hash files); two-mode design with preview then explicit confirmationHook:
SessionStart— auto-initializes.claude/hash.txton first run (no manual setup needed)Planned:
/preserve-session:cleanup— remove stale session folders left by previous renames/preserve-session:scan— scan a directory for unregistered projects and bulk-initialize themMotivation
Claude Code identifies projects by their directory path. Renaming or moving a project causes all previous session history to become unreachable via
--resumeor--continue. This is a well-documented pain point:--resumeshould not require matching working directory--resumefails to find session from different directoryHow it works — UUID-based registry, slug algorithm, copy/rename/merge handling
Each project gets a UUID in
<project>/.claude/hash.txtvia theSessionStarthook. The registry maps UUID → path. When a directory is renamed or moved:/preserve-session:fixreads the UUID, looks up the old path in the registry.jsonlfiles are merged automatically./preserve-session:inheritcan then copy session files from another project into the current oneThe slug algorithm matches Claude Code exactly: all non-alphanumeric, non-hyphen characters (including
_and spaces) are replaced with-.Design decisions — explicit recovery, copy heuristic, slug collision, uninstall safety
/fix.mdfiles as thin triggers — all decision-making is in.shfiles; Claude acts only as a trigger, not a decision-maker--forcepattern — when two non-ASCII paths map to the same slug, operations warn and block rather than silently mixing sessions. Claude mediates confirmation and reruns with--forceif the user acknowledges./preserve-session:uninstallalways runs a dry-run first. Claude enforces a hard gate: Step 3 (--confirm) is only executed after an explicit affirmative in a separate user message. The command file uses a negative-keyword list ("아니", "no", "취소" …) in addition to the positive list to handle ambiguous responses.Shared helpers (
hooks/common.sh) — Python discovery, NFC slug, atomic registry writeAll scripts source a common file containing:
find_python()— fallback chain to find a usable Python 3.6+ interpreter (needed whenuvinterceptspython3)path_to_slug()— NFC-normalized slug matching Claude Code's algorithm (fixes macOS NFDrealpathmismatch)uuidgen_cross()— cross-platform UUID generation (uuidgen→/proc/sys/kernel/random/uuid→ Python)check_slug_collision()— detect registry entries sharing the same slug as a given pathregistry_write()— atomic registry update usingfcntl.LOCK_EX+tempfile.mkstemp+os.replace(). Supportsstrictmode (error on missing/corrupt registry) and graceful mode (starts from{}) for first-run and bulk-init contexts. macOS has noflockbinary, so locking is done entirely in Python.Security — heredoc isolation, JSON guards, symlink protection
PRESERVE_*environment variables with quoted<<'PYEOF'delimiters — no shell variable interpolation inside Python blocksinherit --fromresolvesSOURCE_PATHviarealpathand guards against self-copyJSONDecodeError/OSError/FileNotFoundErrorto prevent data loss and surface actionable error messages on corruptionuninstall.shguards against symlink attacks ([[ -L ]]check beforerm), non-string registry values (isinstance(path, str)), and non-dict top-level JSON (isinstance(r, dict)) before iteratingTest plan — 24 scenarios verified (rename, move, copy, slug collision, uninstall, E2E)
hash.txtand registers in registry on first runhash.txtexists but registry entry is missinghash.txtis empty/preserve-session:fix— already up to date case/preserve-session:fix— rename/move: sessions folder renamed, registry updated/preserve-session:fix— rename/move with destination collision: sessions merged automatically/preserve-session:fix— copy: new UUID assigned, original untouched/preserve-session:fix— slug collision: warns and blocks; proceeds with--force/preserve-session:inherit— lists projects with correct session counts and/preserve-session:inherit— copies sessions from source to current project/preserve-session:inherit— slug collision: warns and blocks; proceeds with--force/preserve-session:doctor— all diagnostics display correctly including path encoding and slug collision items/preserve-session:doctor— registry health shows stale paths/preserve-session:scan— lists unregistered projects under a directory/preserve-session:scan— bulk-initializes selected paths with hash.txt and registry entries/preserve-session:uninstall— preview mode shows registry + all hash.txt paths, makes no changes/preserve-session:uninstall—--confirmdeletes registry, lock file (if present), and all listed hash.txt files/preserve-session:uninstall—~/.claude/projects/(session data) is not touchedfind_python()fallback locates system Python correctlyfix→inherit→claude --resumeshows inherited sessionsPlugin metadata
preserve-session1.1.1plugins/preserve-session/