claude‑sync — Secure, Incremental Sync for Claude Code Conversations
Project Overview
claude‑sync is a command‑line tool that lets you keep your Claude Code conversations, agents, and settings in sync across any number of machines.
Instead of relying on a proprietary cloud service, the tool encrypts everything locally and stores the ciphertext in a backend you choose—Git, Amazon S3, Google Cloud Storage, or Cloudflare R2.
Because the data is encrypted before it leaves your computer, you retain full control over your private coding context while still enjoying a seamless multi‑device workflow.
Key Features
| Feature | Why it matters |
|---|---|
| Multi‑backend support | Use the storage you already trust (Git repos, S3 buckets, etc.). |
| End‑to‑end encryption | AES‑256‑CBC encryption with a locally generated key guarantees that the storage provider never sees plaintext. |
| Incremental synchronization | Only resources whose content has changed since the last sync are uploaded, saving bandwidth and time. |
| Conflict detection & resolution | When the same resource is edited on two devices, hashes are compared and the CLI prompts you to choose a resolution. |
| Generic resource sync | Sessions, agents, and user settings are treated uniformly, keeping your whole Claude Code environment consistent. |
| Extensive test coverage | 172 automated tests give confidence that sync, encryption, and conflict handling work reliably. |
Architecture Overview
+----------------+ +----------------+ +-------------------+
| Claude Code | --> | claude‑sync CLI| --> | Encryption Module |
+----------------+ +----------------+ +-------------------+
|
v
+-------------------+
| Storage Backend |
| (Git / S3 / GCS / |
| R2) |
+-------------------+The CLI orchestrates the flow, the Encryption Module handles all cryptographic work, and the chosen backend stores only encrypted blobs.
How It Works
1. Discovering Local Resources
findLocal() walks the workspace, reads the sync‑state file, and returns only those resources that have changed since the previous sync.
The change detection is based on a hash of the file’s content, not just modification timestamps.
// src/commands/push.ts (excerpt)
import { readFileSync, statSync } from "fs";
import { glob } from "glob";
import { getStoredHash, setStoredHash } from "../utils/syncState.js";
/**
* Returns paths of resources whose content hash differs from the last sync.
*/
export async function findLocal(): Promise<string[]> {
const files = await glob("**/*.json", {
ignore: ["**/node_modules/**", "**/.git/**"],
});
return files.filter((path) => {
const content = readFileSync(path);
const currentHash = hashContent(content);
const previousHash = getStoredHash(path);
return currentHash !== previousHash;
});
}hashContent creates a SHA‑256 digest of the raw bytes; the digest is stored locally after a successful push.
2. Encrypting Data
Encryption lives in src/crypto/encrypt.ts. The module exports both encrypt (which returns ciphertext prefixed with the IV) and hashContent (used by the sync‑state logic).
// src/crypto/encrypt.ts
import { createCipheriv, randomBytes, createHash } from "crypto";
import { getKey } from "./key.js";
export async function encrypt(data: Buffer): Promise<Buffer> {
const key = await getKey(); // 32‑byte AES‑256 key
const iv = randomBytes(16); // 16‑byte initialization vector
const cipher = createCipheriv("aes-256-cbc", key, iv);
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
// Store IV in front of ciphertext for later decryption
return Buffer.concat([iv, encrypted]);
}
/** Simple SHA‑256 hash used for incremental sync */
export function hashContent(data: Buffer): string {
return createHash("sha256").update(data).digest("hex");
}3. Pushing a Single Resource
The pushResource() helper reads a file, encrypts it, uploads the ciphertext, and finally updates the stored hash.
// src/commands/push.ts (excerpt)
import { readFileSync } from "fs";
import { encrypt, hashContent } from "../crypto/encrypt.js";
import { backend } from "../backends/index.js";
import { setStoredHash } from "../utils/syncState.js";
export async function pushResource(path: string): Promise<void> {
const plain = readFileSync(path);
const encrypted = await encrypt(plain);
await backend.pushResource(path, encrypted);
setStoredHash(path, hashContent(plain));
}4. Pulling with Conflict Detection
When pulling, the CLI compares remote and local hashes. If they differ, the user is prompted to keep the local version, accept the remote version, or merge manually.
// src/commands/pull.ts (excerpt)
import ora from "ora";
import chalk from "chalk";
import { backend } from "../backends/index.js";
import { decrypt } from "../crypto/decrypt.js";
import { getStoredHash, setStoredHash } from "../utils/syncState.js";
async function pullResource(path: string, verbose = false) {
const remote = await backend.pullResource(path);
const decrypted = await decrypt(remote);
const remoteHash = hashContent(decrypted);
const localHash = getStoredHash(path);
if (localHash && localHash !== remoteHash) {
// Conflict – ask the user (simplified for brevity)
console.log(chalk.yellow(`Conflict detected for ${path}`));
// ...prompt logic would go here...
}
// No conflict or user chose remote version
// Write file and update stored hash
// (writeFileSync omitted for clarity)
setStoredHash(path, remoteHash);
}The --verbose flag (added in v0.4.0) prints the spinner text and any conflict details.
Getting Started
Install the CLI
BASHnpm install -g @xxdesmus/claude-syncInitialize a backend
BASHclaude-sync initYou’ll be asked to select a storage provider and to generate an encryption key.
Add the Git‑hook integration (optional but recommended)
BASHclaude-sync installThis registers pre‑commit and post‑checkout hooks so that changes are synced automatically.
Push your first changes
BASHclaude-sync pushOnly resources that have changed since the last sync are uploaded.
Pull updates on another machine
BASHclaude-sync pull
Recent Developments
Version 0.4.0 introduced two notable improvements:
--verboseflag forpull– provides detailed progress output and prints conflict information, making troubleshooting easier.- Incremental sync refinements – the push pipeline now hashes each file before upload, stores the hash in a local sync‑state file, and skips uploading unchanged resources. Empty files are also ignored, reducing unnecessary network traffic.
These updates continue the project’s focus on efficiency and a smooth developer experience while keeping the core privacy guarantees intact.
Conclusion
claude‑sync empowers developers to work with Claude Code on any machine without sacrificing privacy or productivity. By encrypting data locally, supporting multiple storage backends, and only moving what truly changed, it offers a lightweight yet robust solution for synchronizing AI‑assisted coding sessions.
The project is open‑source, well‑tested, and welcomes contributions—check out the repository, read the CONTRIBUTING.md, and start keeping your AI‑driven workflow in perfect sync.