Skip to content

Add preserve-session plugin#1347

Closed
wonbywondev wants to merge 1 commit intoanthropics:mainfrom
wonbywondev:add-preserve-session
Closed

Add preserve-session plugin#1347
wonbywondev wants to merge 1 commit intoanthropics:mainfrom
wonbywondev:add-preserve-session

Conversation

@wonbywondev
Copy link
Copy Markdown

What this PR does

Adds preserve-session to claude-plugins-official as an external community plugin, sourced from wonbywondev/claude-plugins at plugins/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:

  • Closes anthropics/claude-code#5768
  • Original submission attempt: anthropics/claude-code#39148 — opened 2026-03-26 to add this as a bundled plugin in 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-session solves 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 confirmation

Hook: SessionStart — auto-initializes .claude/hash.txt on 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 them

Motivation

Claude Code identifies projects by their directory path. Renaming or moving a project causes all previous session history to become unreachable via --resume or --continue. This is a well-documented pain point:

Issue Title State
#38089 --resume should not require matching working directory Open
#28745 Allow resuming conversations from different directories Open
#27883 --resume fails to find session from different directory Closed (duplicate)
#26766 Allow seamless session resumption by name across directories Open
#5768 Resuming sessions only works from the directory they were started Open
How it works — UUID-based registry, slug algorithm, copy/rename/merge handling
~/.claude/
├── project-registry.json      # { "{uuid}": "/current/path", ... }
└── projects/
    └── -Users-your-project/   # Claude Code's path-based sessions folder
        └── *.jsonl

Each project gets a UUID in <project>/.claude/hash.txt via the SessionStart hook. The registry maps UUID → path. When a directory is renamed or moved:

  1. /preserve-session:fix reads the UUID, looks up the old path in the registry
  2. Rename/move: old path is gone → renames the sessions folder + updates registry. If the destination folder already exists (e.g. a session was started before running fix), .jsonl files are merged automatically.
  3. Copy: old path still exists → assigns a new UUID to the copy (original untouched)
  4. /preserve-session:inherit can then copy session files from another project into the current one

The 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
  • No automatic recovery on SessionStart — rename/move detection requires user intent; auto-recovery on session start could silently corrupt state if the old and new paths are both active simultaneously
  • Copy detection heuristic — if the registered path still exists, it's a copy; if gone, it's a rename/move. This handles chains (A → A' copy → delete A → A'' copy of A') correctly at each step
  • Registry is append-only by default — stale entries are harmless (path-existence checked on use); only the current project's old entry is cleaned up during /fix
  • Shell scripts for logic, .md files as thin triggers — all decision-making is in .sh files; Claude acts only as a trigger, not a decision-maker
  • Slug collision detection with --force pattern — 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 --force if the user acknowledges.
  • Uninstall preview-then-confirm/preserve-session:uninstall always 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 write

All scripts source a common file containing:

  • find_python() — fallback chain to find a usable Python 3.6+ interpreter (needed when uv intercepts python3)
  • path_to_slug() — NFC-normalized slug matching Claude Code's algorithm (fixes macOS NFD realpath mismatch)
  • 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 path
  • registry_write() — atomic registry update using fcntl.LOCK_EX + tempfile.mkstemp + os.replace(). Supports strict mode (error on missing/corrupt registry) and graceful mode (starts from {}) for first-run and bulk-init contexts. macOS has no flock binary, so locking is done entirely in Python.
Security — heredoc isolation, JSON guards, symlink protection
  • All Python heredocs use PRESERVE_* environment variables with quoted <<'PYEOF' delimiters — no shell variable interpolation inside Python blocks
  • inherit --from resolves SOURCE_PATH via realpath and guards against self-copy
  • All Python registry read and write blocks handle JSONDecodeError / OSError / FileNotFoundError to prevent data loss and surface actionable error messages on corruption
  • uninstall.sh guards against symlink attacks ([[ -L ]] check before rm), non-string registry values (isinstance(path, str)), and non-dict top-level JSON (isinstance(r, dict)) before iterating
Test plan — 24 scenarios verified (rename, move, copy, slug collision, uninstall, E2E)
  • SessionStart hook creates hash.txt and registers in registry on first run
  • SessionStart hook re-registers if hash.txt exists but registry entry is missing
  • SessionStart hook reinitializes if hash.txt is empty
  • Idempotent: re-running hook on fully initialized project makes no changes
  • /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 ⚠️ collision indicator
  • /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--confirm deletes registry, lock file (if present), and all listed hash.txt files
  • /preserve-session:uninstall~/.claude/projects/ (session data) is not touched
  • Corrupted registry: all scripts surface readable errors without traceback
  • Chain scenario: A → A'(copy) → delete A → A''(copy of A') — each step detected correctly
  • Paths with underscores and spaces: slug matches Claude Code's sessions folder name
  • Non-ASCII paths (Korean): NFC normalization produces correct slug on macOS
  • uv environment: find_python() fallback locates system Python correctly
  • E2E: fixinheritclaude --resume shows inherited sessions

Plugin metadata

Field Value
Name preserve-session
Version 1.1.1
License MIT
Author won (wonbywondev@gmail.com)
Source repo https://github.com/wonbywondev/claude-plugins
Plugin path plugins/preserve-session/
Demo Video

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>
@github-actions
Copy link
Copy Markdown

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.

@wonbywondev
Copy link
Copy Markdown
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 marketplace.json entry. Full design rationale and test plan are available in the related submission anthropics/claude-code#39148.

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.

[BUG] Resuming sessions only works from the directory in which they were started

1 participant