Claude Codeのメモリを複数台のMacで同期する方法 〜Syncthing + symlink構成〜

AIコーディングツールのClaude Codeには、作業の経緯や約束事を記録して次回のセッションで自動的に読み込んでくれる「メモリ」機能があります。プロジェクトごとに~/.claude/projects/<slug>/memory/へMarkdownで保存される便利な仕組みですが、このメモリはマシンローカルで、複数台のPCで作業していると「片方のPCで覚えたことがもう片方に引き継がれない」という問題があります。
今回はSyncthingとsymlinkを組み合わせて、複数台のMac間でメモリと個人スキルを常時同期する構成を構築しました。公式設定やOSSも検討したので、その過程も含めてまとめます。
目次
今回の構成と課題
- MacBook Pro(メインの作業マシン)
- Mac mini(LAN内のサブマシン)
- 目標:今後3台目以降も追加できること・LAN外でも同期できること・GitHub等にメモリを置かないこと
まず前提として、Claude Codeのメモリの保存先は次のようにプロジェクトのパスから計算された「slug」名のディレクトリになります。
~/.claude/projects/-Users-kawai-dev-my-project/memory/
├── MEMORY.md # インデックス(毎セッション自動読み込み)
└── *.md # トピックごとのメモリファイル
slugは「プロジェクトルートの絶対パスの非英数字を-に置き換えたもの」なので、PCごとにリポジトリのパスが違うとslugも変わり、別のメモリ扱いになります。また、メモリはマシンローカルと公式ドキュメントに明記されており、同期機能は提供されていません。
公式設定 autoMemoryDirectory は不採用だった
「メモリの保存先を変えられる公式設定があるのでは?」ということで、まずautoMemoryDirectoryという設定を実機で検証しました。結論から言うとこの設定は全プロジェクト共通の単一ディレクトリを指すため、業務プロジェクトと個人プロジェクトでメモリが混在してしまいます。
メモリはプロジェクトごとに分離されているからこそ文脈が汚れず便利に使えるので、この挙動は要件と合いませんでした。プロジェクト別の分離を保ったまま同期するには、自前で構成を作る必要があります。
gitベースのOSSも検討した
続けてOSSを調査したところ、turbyho/claude-memory-syncというgitベースの同期ツールが見つかりました。Claude Codeのhooks(セッション開始時にpull・終了時にpush)でメモリをgitリポジトリに同期するもので、既存メモリを自動マージするmerge_into()の設計など、よくできていました。
ただしこの方式は中央のgitリポジトリ(GitHub等)が必須です。今回の要件は「業務プロジェクトのメモリを外部サービスに置きたくない」「そもそもGitHubにコミットしたくない」というものでした。また、複数台・LAN外同期を考えるとpush/pullの運用も手間になります。
ということでOSSは使わず、設計の一部(既存メモリの自動マージ・衝突時に両方残す)だけ参考にさせていただくことにしました。
採用した構成:Syncthing + symlink
採用したのはSyncthing(P2Pファイル同期)+ symlinkの組み合わせです。全PCで~/claude-memory/という同一パスのフォルダをSyncthingで常時同期し、各PCのClaude Codeのメモリディレクトリはそのフォルダへのsymlinkにします。
~/claude-memory/ ← Syncthing同期フォルダ(全PC同一パス)
├── projects/my-project/ ← プロジェクトのメモリ実体
│ ├── MEMORY.md
│ └── *.md
├── skills/terraform-env-plan/ ← 個人スキル(全プロジェクト共通)
│ └── SKILL.md
├── link-project.sh ← symlinkを張るヘルパースクリプト
└── README.md ← 3台目以降のセットアップ手順
各PCのClaude Code側:
~/.claude/projects/-Users-kawai-dev-my-project/memory → ~/claude-memory/projects/my-project (symlink)
~/.claude/skills/terraform-env-plan → ~/claude-memory/skills/terraform-env-plan (symlink)
- slugの違いをsymlinkで吸収:slugはPCごとのパスから計算されるが、symlinkの向き先は固定なので、PCのパスが違っても同じメモリを共有できる
- 中央サーバ不要:SyncthingはP2P。global discoveryでNAT越しが自動、同一LANではTCP直結になる
- 何もしなくて同期される:Claude Codeはメモリディレクトリに普通に書き込むだけで、実体がsymlink先なので自動的に同期される。hooksも不要
- 上書き保護:フォルダのversioningをtrashcan(30日)にしているので、上書きされた旧バージョンも
.stversions/に残る
個人スキル(Claude Codeの~/.claude/skills/に置く自分用のskill)も同じフォルダに置いて全プロジェクト・全PCで共有するようにしました。プロジェクト側の.claude/skills/に個人用ファイルを置く運用(.git/info/excludeで隠す方式)をやめ、こちらに一本化しています。
link-project.sh でsymlinkを張る
各PCでやることは「sh ~/claude-memory/link-project.sh <プロジェクト名> <リポジトリパス>を1回実行する」だけです。スクリプトは次のとおり(macOS向け)。
#!/bin/sh
# link-project.sh — Claude Codeのプロジェクトメモリを~/claude-memoryに接続する
# Usage: link-project.sh <project-name> <project-path>
set -eu
NAME=$1
ROOT_INPUT=$2
HOST=$(scutil --get LocalHostName 2>/dev/null || hostname -s)
DEST="$HOME/claude-memory/projects/$NAME"
BACKUP="$HOME/claude-memory/.backup"
# gitリポジトリならcommon dirからルートを検出(worktreeやサブディレクトリ起動の
# セッションもルートのメモリに統合される・Claude Codeのslug計算と同じ考え方)
common=$(git -C "$ROOT_INPUT" rev-parse --path-format=absolute --git-common-dir 2>/dev/null || true)
if [ -n "$common" ] && [ "$(basename "$common")" = ".git" ]; then
ROOT=$(cd "$(dirname "$common")" && pwd)
else
ROOT=$(cd "$ROOT_INPUT" && pwd)
fi
# Claude Codeと同じslug計算
SLUG=$(printf '%s' "$ROOT" | LC_ALL=C sed 's/[^A-Za-z0-9]/-/g')
MEM="$HOME/.claude/projects/$SLUG/memory"
# 既存メモリがあれば~/claude-memory側にマージ(衝突は<name>.<HOST>.mdで両方残す)
merge_into() {
for f in "$1"/* "$1"/.[!.]*; do
[ -e "$f" ] || continue
b=$(basename "$f")
if [ ! -e "$2/$b" ]; then
cp -R "$f" "$2/$b"
elif [ -d "$f" ] && [ -d "$2/$b" ]; then
merge_into "$f" "$2/$b"
elif [ -f "$f" ] && ! cmp -s "$f" "$2/$b"; then
cp "$f" "$2/${b%.md}.$HOST.md"
fi
done
}
if [ -L "$MEM" ]; then
echo "既にsymlinkです(何もしません)"
elif [ -d "$MEM" ]; then
merge_into "$MEM" "$DEST"
mkdir -p "$BACKUP"
BK="$BACKUP/${SLUG}.$(date +%Y%m%d%H%M%S)"
mv "$MEM" "$BK" # 元メモリはバックアップ
ln -s "$DEST" "$MEM"
else
mkdir -p "$(dirname "$MEM")"
ln -s "$DEST" "$MEM"
fi
# skills/配下も~/.claude/skills/にsymlink
for s in "$HOME/claude-memory/skills"/*; do
[ -d "$s" ] || continue
sn=$(basename "$s")
[ -L "$HOME/.claude/skills/$sn" ] || [ -e "$HOME/.claude/skills/$sn" ] || ln -s "$s" "$HOME/.claude/skills/$sn"
done
ポイントは次の3つです。
- プロジェクト名はPCに依存しない識別名を使う(第1引数)。全PCで統一する
- 既存メモリがある場合は衝突ファイルを両方残してマージし、元はタイムスタンプ付きでバックアップするので、上書きで失うものがない
- slugの計算は
LC_ALL=Cでsedする。ロケールによって挙動が変わるのを避けるため
新しいPCを追加する手順
3台目以降のPCは、同期フォルダ内に手順書(README.md)を同梱しているので、それに従えば追加できます。
- Syncthingをインストールして常駐化:Homebrewがあるなら
brew install syncthing && brew services start syncthing。なければ公式バイナリを~/binに置いてLaunchAgentでsyncthing serve --no-browser --no-restartを常駐させる - 既存デバイスと相互承認:Web UI(
http://127.0.0.1:8384)でデバイスIDを相互に登録する - フォルダを受け入れる:既存PCから「claude-memory」フォルダの共有が届くので、
~/claude-memoryとして受け入れる(versioningはtrashcan 30日に設定) - link-project.shを実行:
sh ~/claude-memory/link-project.sh <プロジェクト名> <リポジトリパス> - 新しいClaude Codeセッションを開く:hooksやメモリはセッション開始時に読まれるので、開き直せば反映されている
運用の注意点(ハマりどころ含む)
- 誤って削除・上書きしたときも30日以内なら復元できる:versioningをtrashcan(30日)にしているため、削除・上書きされた旧バージョンは各PCの
.stversions/に退避されます。実際にテストファイルで削除→退避→復元を試したところ、削除は相手PCの.stversions/に退避され、そこから復元すると約10秒で両PCに復活しました。ただし退避先は変更を受信した側のPCです(編集したPC自身の.stversions/には入らないので、復元するときはもう片方のPCを見る)。また、短時間に連続して上書きすると中間のバージョンは残らないことがあるため、確実な保険としてTime Machineなどの別経路バックアップとの併用がおすすめです - 競合時はデータが消失しない:同じファイルを複数PCで同時編集すると
.sync-conflict-YYYYMMDD-*.mdが生成される(Syncthingの仕様)。次のセッションでClaudeに「sync-conflictファイルをマージして」と頼めばよい - SyncthingのGUIポートは8384:8380と間違えやすい(自分は最初接続先を間違えて0バイト応答でハマった)。設定は
~/Library/Application Support/Syncthing/config.xmlにあり、REST APIはX-API-Keyヘッダが必要 - 同期フォルダにcredentials等を入れない:メモリやskillの Markdown のみ。トークン・transcript(
*.jsonl)等は入れない運用にしている - autoMemoryDirectoryは使わない:前述のとおり全プロジェクト共通ディレクトリになるため。実機検証済み
- デバイスを廃止するとき:Syncthingからデバイスを削除する。symlinkは各PCに残るので手動で後片付けする
まとめ
Claude Codeのメモリは公式の同期機能がないものの、「全PC同一パスの同期フォルダ + symlink」というシンプルな構成で、プロジェクト別の分離を保ったまま複数台で共有できました。hooksや追加ツールも不要で、Claude Codeがメモリに書き込むだけで自動的に全PCへ伝播します。
AIコーディングツールの「記憶」を資産として育てるうえで、PCをまたいで失わない仕組みはかなり効果的です。同じ課題を持っている方の参考になれば幸いです。

![[Swift]WatchKitで”error: WatchKit Extension doesn’t contain any WatchKit apps whose bundle identifiers match 〜”と怒られた](https://blog.takezou.com/wp-content/uploads/2015/04/applewatch-720x405.png)
