Current release: v2.1.0 (see changelog)
A config-driven system for backing up and restoring your complete Claude Code environment — settings, memory, skills, plugins, user-content directories (plans, commands, agents, output-styles, rules, hooks, scheduled-tasks), sessions, subagent transcripts, tool-result payloads, and more.
- Overview
- What to Backup
- What NOT to Backup
- Quick Start
- Detailed Instructions
- Scheduling Automatic Backups
- Sanitized Export for Sharing
- Security
- Troubleshooting
- FAQ
- Changelog
- Upgrading
- Versioning
Claude Code stores a rich set of data across your machine: global instructions, per-project memory, installed skills, plugins, saved plans, custom commands, subagents, output styles, rules, hooks, scheduled tasks, session transcripts, subagent transcripts, tool-result payloads, and MCP server configurations. Losing any of these means rebuilding your environment from scratch.
This guide provides three scripts that back up the full Claude Code data model to a private Git repository:
init.sh— First-time setup. Scans your projects, generatesbackup-config.json.backup.sh— Config-driven, modular backup with auto-commit and optional auto-push. Fully non-interactive (safe for cron).restore.sh— Interactive restore with per-category prompts (--yesfor unattended;--dry-runto preview without writing).
Requirements: bash, git, jq
The v2 system backs up every category of Claude Code data that matters for portability:
| Category | Source Location | Backup Directory | Description |
|---|---|---|---|
| Global instructions | ~/.claude/CLAUDE.md |
global/ |
Custom instructions for all sessions |
| Extra context files | ~/.claude/*.md |
global/ |
Additional markdown context files |
| Settings | ~/.claude/settings.json |
global/ |
Basic settings (e.g., alwaysThinkingEnabled) |
| Local settings | ~/.claude/settings.local.json |
global/ |
Permission rules and advanced settings |
| Keybindings | ~/.claude/keybindings.json |
global/ |
Custom key bindings |
| MCP config | ~/.claude.json |
global/claude.json |
MCP server definitions, OAuth tokens, app state |
| Skills | ~/.claude/skills/ |
skills/ |
User-installed skill packages |
| Plugins | ~/.claude/plugins/ |
plugins/ |
Plugin registry (installed_plugins.json, blocklist.json, known_marketplaces.json) |
| Plans | ~/.claude/plans/ |
plans/ |
Saved implementation plans |
| Custom commands | ~/.claude/commands/ |
commands/ |
User-defined slash commands |
| Subagents | ~/.claude/agents/ |
agents/ |
Custom subagent definitions (v2.1+) |
| Output styles | ~/.claude/output-styles/ |
output-styles/ |
Custom output style definitions (v2.1+) |
| Rules | ~/.claude/rules/ |
rules/ |
Topic-scoped instruction files (v2.1+) |
| Hooks | ~/.claude/hooks/ |
hooks/ |
Hook scripts referenced by settings.json (v2.1+) |
| Scheduled tasks | ~/.claude/scheduled-tasks/ |
scheduled-tasks/ |
User-defined scheduled task skills (v2.1+) |
| Todos | ~/.claude/todos/ |
todos/ |
Per-session task state |
| Project memory | ~/.claude/projects/<name>/memory/ |
projects/<name>/memory/ |
Auto memory files (MEMORY.md, topic files) |
| Session transcripts | ~/.claude/projects/<name>/*.jsonl |
projects/<name>/sessions/ |
Session data for /resume support |
| Subagent transcripts | ~/.claude/projects/<name>/<session-uuid>/subagents/ |
projects/<name>/subagents/<session-uuid>/ |
Transcripts for subagents spawned by each session (v2.1+) |
| Tool-result payloads | ~/.claude/projects/<name>/<session-uuid>/tool-results/ |
projects/<name>/tool-results/<session-uuid>/ |
Large tool-call results referenced by the session transcript (v2.1+) |
The user-content categories marked v2.1+ (plans, commands, agents, output-styles, rules, hooks, scheduled-tasks) are driven by a shared USER_CONTENT_DIRS array in backup.sh and restore.sh. Adding a new category when Claude Code ships one takes a single-line edit per script.
These are excluded because they are sensitive, machine-specific, or regenerated automatically:
| File / Directory | Reason to Exclude |
|---|---|
.credentials.json |
Authentication tokens (regenerated on login) |
history.jsonl |
Global prompt history — potentially huge and duplicated by session transcripts |
file-history/ |
File operation history (machine-specific) |
debug/ |
Debug logs and temporary data |
statsig/, telemetry/, cache/ |
Analytics and cache data |
plugins/cache/, plugins/marketplaces/ |
Re-downloaded on next launch |
tasks/ |
Runtime state (lock files, high-watermarks) for in-flight background agents |
backups/ |
Claude Code's own internal session auto-backups |
ide/, session-env/, shell-snapshots/, paste-cache/ |
Per-session ephemeral state |
git clone https://github.com/jtklinger/claude-code-backup-guide.git
cd claude-code-backup-guide# Create a new directory for your backups (separate from this guide repo)
mkdir ~/claude-code-backup
cd ~/claude-code-backupThe init script scans your ~/.claude/projects/ directory, lets you select which projects to include, and generates backup-config.json:
bash /path/to/claude-code-backup-guide/scripts/init.sh ~/claude-code-backupYou will see a numbered list of discovered projects. Type all, none, or specific numbers separated by spaces.
bash /path/to/claude-code-backup-guide/scripts/backup.sh ~/claude-code-backupThe script copies all configured data categories, stages changes, and commits automatically.
cd ~/claude-code-backup
git remote add origin git@github.com:YOUR_USER/claude-code-backup.git
git push -u origin maingit clone git@github.com:YOUR_USER/claude-code-backup.git
cd claude-code-backup
# Preview what would change without writing anything (recommended first run
# on a laptop that already has a Claude Code install):
bash /path/to/claude-code-backup-guide/scripts/restore.sh . --dry-run
# Interactive restore (prompts for each category)
bash /path/to/claude-code-backup-guide/scripts/restore.sh .
# Or non-interactive (restore everything)
bash /path/to/claude-code-backup-guide/scripts/restore.sh . --yes--dry-run scans every file the restore would touch and classifies it as
NEW (destination doesn't exist), CHANGED (destination differs), or
SAME (identical to backup). Nothing is written. Run this before a real
restore to see exactly which existing files would be overwritten.
The config file lives in the root of your backup repository. It controls what gets backed up:
{
"version": 1,
"claude_dir": "~/.claude",
"include_sessions": true,
"include_todos": true,
"projects": [
"C--Users-jtkli-projects-Homelab",
"C--Users-jtkli-projects-my-app"
],
"git_auto_push": false,
"git_remote": "origin",
"git_branch": "main"
}| Field | Type | Description |
|---|---|---|
version |
number | Config format version (must be 1) |
claude_dir |
string | Path to Claude Code data directory (~ is expanded) |
include_sessions |
boolean | Whether to back up .jsonl session transcripts per project |
include_todos |
boolean | Whether to back up per-session todo state |
projects |
string[] | List of project directory names from ~/.claude/projects/ |
git_auto_push |
boolean | Push to remote after each backup commit |
git_remote |
string | Git remote name for auto-push |
git_branch |
string | Git branch name for auto-push |
Project directory names use Claude Code's encoding scheme where path separators become dashes (e.g., C--Users-jtkli-projects-Homelab represents C:/Users/jtkli/projects/Homelab). The init.sh script handles this mapping for you.
After running a backup, your repository will look like this:
claude-code-backup/
├── backup-config.json
├── .gitignore
├── global/
│ ├── CLAUDE.md
│ ├── settings.json
│ ├── settings.local.json
│ ├── keybindings.json
│ ├── claude.json # ~/.claude.json (MCP config + OAuth)
│ └── HOMELAB-CONTEXT.md # any extra *.md files
├── skills/
│ └── <skill-packages>/
├── plugins/
│ ├── installed_plugins.json
│ ├── blocklist.json
│ └── known_marketplaces.json
├── plans/
│ └── <plan>.md
├── commands/
│ └── <command>.md
├── agents/ # v2.1+: custom subagent definitions
├── output-styles/ # v2.1+: custom output style definitions
├── rules/ # v2.1+: topic-scoped instructions
├── hooks/ # v2.1+: hook scripts referenced by settings.json
├── scheduled-tasks/ # v2.1+: user-defined scheduled task skills
│ └── <task-name>/
│ └── SKILL.md
├── todos/
│ └── <session-id>.json
└── projects/
└── C--Users-jtkli-projects-Homelab/
├── memory/
│ ├── MEMORY.md
│ └── <topic>.md
├── sessions/
│ ├── <session-id>.jsonl
│ └── <session-id>.meta.json
├── subagents/ # v2.1+: subagent transcripts, one dir per parent session
│ └── <session-id>/
│ ├── agent-<id>.jsonl
│ └── agent-<id>.meta.json
└── tool-results/ # v2.1+: large tool-call payloads
└── <session-id>/
└── toolu_<id>.txt
On restore, the backup's subagents/<session-id>/ and tool-results/<session-id>/ directories are replayed back to ~/.claude/projects/<name>/<session-id>/{subagents,tool-results}/ to match Claude Code's on-disk layout.
Usage: bash init.sh [backup-directory]
- Creates the backup directory if it does not exist
- Initializes a Git repository
- Copies the
.gitignoretemplate - Scans
~/.claude/projects/and presents an interactive project selector - Generates
backup-config.json - Only needs to be run once (re-run to add new projects)
Usage: bash backup.sh [backup-directory] [--sanitize <output-directory>]
- Reads
backup-config.jsonfrom the backup directory - Copies all data categories (global settings, MCP config, skills, plugins, user-content dirs, todos, project data including subagent transcripts and tool-result payloads)
- Only copies files that have changed (uses
cmpfor diffing) - Removes stale files from the backup that no longer exist in the source
- Stages, commits, and optionally pushes
- Fully non-interactive — safe for cron or Task Scheduler
- If no backup-directory argument is provided, assumes the script's parent directory is the backup repo
--sanitize <output-directory>produces a credential-free export in the given directory (see Sanitized Export)
Usage: bash restore.sh [backup-directory] [--yes] [--dry-run]
- Reads
backup-config.jsonfrom the backup directory - Prompts interactively for each category (global settings, MCP config, skills, plugins, user-content dirs, todos, projects); only prompts for user-content categories that actually have files in the backup
- Creates timestamped backups of existing files before overwriting (e.g.
settings.json.backup.20260421_142301) --yesflag skips all prompts and restores everything--dry-run(aliased-n) scans every file and classifies it as NEW, CHANGED, or SAME without writing anything. Implies--yes. Use this before a real restore to see exactly which existing files would be overwritten.- Session files are restored from
sessions/subdirectory back to the project root (where Claude Code expects them) - Subagent transcripts and tool-result payloads are restored to
projects/<name>/<session-id>/{subagents,tool-results}/to match Claude Code's on-disk layout - Prints a verification summary with file counts and sizes (or a NEW/CHANGED/SAME summary in dry-run mode)
Since backup.sh is fully non-interactive and auto-commits, it works well as a scheduled task.
# Run backup daily at 2:00 AM
crontab -e
# Add this line:
0 2 * * * /path/to/scripts/backup.sh /path/to/claude-code-backup >> /tmp/claude-backup.log 2>&1- Open Task Scheduler
- Create a new task:
- Trigger: Daily (or at logon, or whatever frequency you prefer)
- Action: Start a program
- Program:
C:\Program Files\Git\bin\bash.exe - Arguments:
/path/to/scripts/backup.sh /path/to/claude-code-backup
To push to your remote after every backup, edit backup-config.json:
{
"git_auto_push": true,
"git_remote": "origin",
"git_branch": "main"
}The backup script will attempt to push after committing. If the push fails, it logs a warning but does not exit with an error.
The --sanitize flag produces a credential-free copy of your settings, safe for sharing publicly or with teammates.
# Run backup and export sanitized copy
bash scripts/backup.sh ~/claude-code-backup --sanitize ~/claude-export
# Share the export directory
cd ~/claude-export
git init && git add -A && git commit -m "Claude Code settings template"
git remote add origin git@github.com:YOUR_USER/claude-code-template.git
git push -u origin main| Data | Action |
|---|---|
| MCP server hostnames | Replaced with <HOSTNAME> |
| MCP usernames | Replaced with <USERNAME> |
| SSH key paths | Replaced with <SSH_KEY_PATH> |
| Auth tokens / Bearer tokens | Replaced with <AUTH_TOKEN> |
| URLs in MCP args | Replaced with <URL> |
| Environment variable values | Replaced with <REDACTED> |
| File paths in MCP args | Replaced with <PATH> |
| MCP permission names | Server names replaced with <server> |
| OAuth account data | Removed entirely |
| App state (counters, caches) | Removed entirely |
| Sessions and todos | Excluded from export |
- CLAUDE.md and extra context files (as-is)
- Settings structure (plugins, keybindings, preferences)
- MCP server names and types (structure without credentials)
- Skills, plugin registry (as-is)
- User-content directories (plans, commands, agents, output-styles, rules, hooks, scheduled-tasks) — copied as-is. Review before sharing: these files may reference your infrastructure, names, or internal systems. The generated
README.mdin the export flags this. - Per-project memory (interactive selection)
Sessions, todos, subagent transcripts, and tool-result payloads are excluded from the sanitized export (they are conversation-specific and contain content that cannot be safely auto-redacted).
- Copy the export files into
~/.claude/ - Edit
global/claude.json— replace<PLACEHOLDER>values with your server details - Edit
global/settings.json— updatemcp__<server>__*permission entries - Restart Claude Code
The backup includes ~/.claude.json, which contains:
- MCP server configurations with connection details
- OAuth tokens for authenticated MCP servers
- App state and other runtime data
It also includes settings.local.json, which may contain:
- Approved command patterns that reveal your infrastructure
- File paths and directory structures
Never use a public repository for your backup.
Unlike v1, v2 backs up ~/.claude.json and settings.local.json as-is (no sanitization). This is intentional -- sanitized files lose MCP server definitions and cannot be restored without manual reconfiguration.
The trade-off is clear: your backup repo is sensitive and must be private. Treat it like you would a .env file or SSH private key.
If you accidentally push to a public repo or your backup repo is compromised:
- Immediately rotate all OAuth tokens and passwords in your MCP server configurations
- Re-authenticate with Claude Code
- Remove the repository from public access
- Consider using
git filter-branchor BFG Repo-Cleaner to purge history
Use SSH keys (not HTTPS with tokens) for your backup repository remote. This avoids storing additional credentials in your Git config:
git remote add origin git@github.com:YOUR_USER/claude-code-backup.gitProblem: backup.sh or restore.sh exits with "jq is required but not installed"
Solution: Install jq for your platform:
# macOS
brew install jq
# Debian/Ubuntu
sudo apt install jq
# RHEL/Rocky/Fedora
sudo dnf install jq
# Windows (winget)
winget install jqlang.jq
# Windows (scoop)
scoop install jqProblem: Backup repository grows quickly due to .jsonl session transcripts
Solution: Disable session backup if you do not need /resume support across machines:
{
"include_sessions": false
}Or periodically clean old sessions from the backup:
# Remove session files older than 30 days from the backup
find ~/claude-code-backup/projects/*/sessions/ -name "*.jsonl" -mtime +30 -deleteProblem: Per-project memory (MEMORY.md, topic files) not appearing after restore
Solutions:
- Verify the project is listed in
backup-config.jsonunderprojects - Check that the project directory name matches exactly (case-sensitive)
- Restart Claude Code after restoring -- memory files are read on startup
- Verify the files exist:
ls ~/.claude/projects/<project-name>/memory/
Problem: Settings do not take effect after running restore.sh
Solution:
- Restart Claude Code completely (quit and relaunch)
- Check file permissions:
ls -la ~/.claude/ - Verify file contents:
cat ~/.claude/settings.json - Check for
.backup.*files that may indicate the restore created backups of conflicting files
Problem: MCP servers show as disconnected after restoring ~/.claude.json
Solutions:
- OAuth tokens in
~/.claude.jsonmay have expired -- re-authenticate with the affected services - Verify network connectivity to the MCP server endpoints
- Check that any local dependencies (npm packages, binaries) are installed on the new machine
- Run
claude mcp listto see the current status
Problem: backup.sh says "No changes detected" even though you changed settings
Solution: The script compares files byte-for-byte using cmp. If the files are identical, no commit is created. Verify the source files actually changed:
diff ~/.claude/settings.json ~/claude-code-backup/global/settings.jsonA: v1 backed up only CLAUDE.md, settings.json, settings.local.json, and a text dump of MCP servers. v2 is a config-driven system that backs up the complete Claude Code data model: memory, skills, plugins, plans, commands, keybindings, the full ~/.claude.json, session transcripts, and todos. It uses three purpose-built scripts (init, backup, restore) instead of manual copy commands.
v2.1 adds scheduled-tasks, subagent transcripts, tool-result payloads, and future-proofs for new user-content categories (agents, output-styles, rules, hooks). It also adds a --dry-run flag to restore.sh for safely previewing a restore on a machine with an existing Claude Code install. See the Changelog for the full list.
A: No. Chat history (history.jsonl) is excluded intentionally -- it can be hundreds of megabytes and contains the full text of every conversation. If you need to preserve specific conversations, export them individually. Session transcripts (.jsonl in project directories) are a different thing and are backed up for /resume support.
A: No. v2 backs up ~/.claude.json which contains OAuth tokens and MCP server credentials. Your backup repository must be private. If you want to share your CLAUDE.md or settings as a template, copy those specific files into a separate public repository.
A: Set up a cron job or Task Scheduler task to run backup.sh daily. The script is idempotent -- if nothing changed, no commit is created. For critical changes (new MCP servers, major CLAUDE.md updates), run a manual backup immediately.
A: Partially. You can share CLAUDE.md, plans, commands, and skills through a shared private repository. However, ~/.claude.json and settings.local.json contain per-user credentials and should not be shared. Consider maintaining a team template repo alongside individual backup repos.
A: Project-specific CLAUDE.md files live in the project's own repository root and should be committed there, not in your settings backup. This guide backs up per-project memory (auto-generated context) and sessions (for /resume), which live under ~/.claude/projects/.
A: The settings files themselves are platform-agnostic. However, project directory names encode the full path (e.g., C--Users-jtkli-projects-Homelab), so a backup from Windows will have Windows-style encoded paths. If restoring on macOS/Linux, you may need to update the projects list in backup-config.json to match the new machine's paths.
A: Yes. Either re-run init.sh (it will ask before overwriting the config), or manually edit backup-config.json and add the project directory name to the projects array. Find directory names with ls ~/.claude/projects/.
A: The restore script creates timestamped backups of any existing file before overwriting it (e.g., settings.json.backup.20260305_140000). You can always roll back by copying the .backup.* file over the restored version.
- New backed-up categories:
~/.claude/scheduled-tasks/(user-defined scheduled task skills) and per-session nested data —<session-uuid>/subagents/(subagent transcripts) and<session-uuid>/tool-results/(large tool-call payloads). - Future-proofed categories:
agents/,output-styles/,rules/,hooks/— covered automatically if/when they appear under~/.claude/. - New
--dry-runflag onrestore.sh: classifies every file the restore would touch as NEW, CHANGED, or SAME, and writes nothing. Implies--yesso the full picture is shown in one pass. Useful before a real restore on a laptop that already has a Claude Code install. - Refactor: user-content directories (plans, commands, agents, output-styles, rules, hooks, scheduled-tasks) are now driven by a shared
USER_CONTENT_DIRSarray inbackup.shandrestore.sh. Adding a new category takes one line per script. - Sanitized export: the generated README now flags user-content directories for review before sharing (they may reference personal infrastructure).
- No config schema changes — existing
backup-config.json(version1) continues to work without edits.
- Initial config-driven rewrite: three scripts (
init.sh,backup.sh,restore.sh) withbackup-config.json, covering global settings, MCP config, skills, plugins, plans, commands, todos, per-project memory, and session transcripts. - Added
--sanitizeflag onbackup.shto produce credential-free exports.
- Manual copy-based backup covering only CLAUDE.md, settings.json, settings.local.json, and a text dump of MCP servers.
The scripts run directly out of the guide repo — your scheduled task, cron job, or manual command points at /path/to/claude-code-backup-guide/scripts/backup.sh. Upgrading is a two-step git pull:
cd /path/to/claude-code-backup-guide
git pullConfirm the new version on the next run — every script prints its version in the banner:
Claude Code Backup Script v2.1.0
==================================
Compare the banner against the Changelog to confirm you picked up the expected release.
Backups are additive across releases. When v2.1 adds new categories (scheduled-tasks, subagents, tool-results, etc.), the first backup run after upgrade will:
- Create the new top-level directories (
scheduled-tasks/,agents/,output-styles/,rules/,hooks/) — only if you have source data for them under~/.claude/ - Add
subagents/andtool-results/subdirectories inside each backed-up project - Commit the additions with the usual
Backup Claude Code settings -- <timestamp>message
Existing directories (global/, skills/, plans/, commands/, todos/, projects/<hash>/memory/, projects/<hash>/sessions/) are untouched. No data is moved or renamed. No config edits required.
Run the backup twice after upgrade — the first run catches up new categories, the second should report "No changes detected", confirming idempotency.
If your backup repo was produced by an older script and you restore with a newer script (or vice versa), both directions work for v2.0 ↔ v2.1:
| Scenario | Behavior |
|---|---|
v2.0 backup.sh → v2.1 restore.sh |
Restore skips new categories (they don't exist in the backup) — no errors. |
v2.1 backup.sh → v2.0 restore.sh |
Old restore ignores directories it doesn't know about. New categories stay in the backup repo unused until you upgrade the restore side. |
This is safe because v2.1's additions are purely additive. Future major version bumps may break this — the changelog entry for any such release will flag the incompatibility explicitly.
Once we start tagging releases, you can pin your machine to a known-good version:
cd /path/to/claude-code-backup-guide
git checkout v2.1.0 # stay on the exact release
# ... later, when you want to upgrade:
git checkout master && git pullPin if you're running this on multiple machines and want to roll out upgrades deliberately, or if a release causes issues and you want to roll back. Otherwise tracking master is fine — the scripts are small enough that breaking changes will be rare and well-documented.
A major version bump (v2.x → v3.x) may require config migration. The Changelog entry for any such release will spell out:
- Whether
backup-config.jsonneeds field additions or renames (and provide a migration snippet) - Whether existing backup directory layouts need to be reorganized (and provide a migration script)
- Whether old backups become unreadable (and how to archive them)
Until then, upgrades within the v2.x line are safe to do automatically — for example, a cron job that runs git -C /path/to/claude-code-backup-guide pull once a week before the backup.
This tool uses two distinct version numbers, which move independently:
| Version | Where it lives | Bumped when |
|---|---|---|
Script version (SCRIPT_VERSION="2.1.0" in each script, printed in the banner) |
scripts/*.sh |
Every release. Follows SemVer: major for breaking changes to invocation or output layout, minor for new features, patch for bug fixes. |
Config schema version ("version": 1 in backup-config.json) |
backup-config.json, enforced at parse time |
Only when the config file format changes in a backwards-incompatible way. Currently 1. |
If you're reporting an issue, include the script version from the banner (e.g. Claude Code Backup Script v2.1.0) — it's the fastest way to confirm what code you're running.
Found an issue or have an improvement? Please:
- Fork this repository
- Create a feature branch
- Submit a pull request
MIT License - Feel free to use and modify as needed.