📓 memotty

【ctx】Syncthingで2台のPCのエージェント履歴を自動同期する

WSL2 とミニPC(minis)のどちらで Claude Code や Codex を使っても、会話の履歴が相手のPCに自動で届くようにした。両方のPCの ctx で、両方の履歴を検索できる。

ひとことで言うと Syncthing で元のログをお互いのPCへコピーし続け、ctx にはそのコピーも読ませている。

登場するもの #

名前役割
ctxClaude Code や Codex の会話履歴を、手元で検索できるツール
元のログClaude Code や Codex が会話ごとに書き出すファイル。ctx はこれを読む
SyncthingPC 同士でフォルダを直接同期するツール
Tailscale離れた PC 同士をつなぐ仮想 LAN。Syncthing はこの中だけで通信する
WSL2これまでのメイン環境。Windows 上の Ubuntu
minisこれからのメイン環境。ミニPC の Ubuntu

全体像 #

flowchart LR WL[WSL2 で使った履歴] ML[minis で使った履歴] WC[(WSL2 の ctx)] MC[(minis の ctx)] WL -->|そのまま読む| WC ML -.->|Syncthing でコピー| WC ML -->|そのまま読む| MC WL -.->|Syncthing でコピー| MC

実線は、そのPCのログをそのまま読む線。点線は、Syncthing で相手のPCに運んだコピーを読む線。

ルールは3つだけ。

  1. 自分のログは送るだけ、相手のログは受け取るだけ。 両方から同じファイルを書き換えないので、競合しない。
  2. 元のPCで消えても、コピーは消さない。 Claude Code は30日より古いログを消すが、相手のPCには残る。
  3. ctx はコピーも普通の履歴として読む。 ふだんの検索にも日報にも、両方のPCの分が出る。

使い方 #

ふだんどおり検索すれば、両方のPCの履歴が出る。

ctx search "検索したい言葉"

どちらのPCで使った分かを絞るときは、グループを指定する。

ctx search "検索したい言葉" --source-group minis              # minis の分だけ
ctx search "検索したい言葉" --source-group torrent-nano-wsl2  # WSL2 の分だけ

日報づくりで使う ctx list events も、設定を変えずに両方のPCの分を返す。

2026-09-05 以前の履歴 #

この期間は元のログがもう無いので、Syncthing では運べない。minis には、ctx の書き出し機能(archive)で別の場所に入れてある。検索するときは、置き場所を指定する。

ctx --data-root ~/ctx-archive/torrent-nano-wsl2 search "検索したい言葉"

どこに何があるか #

ものWSL2minis
自分のログ~/.claude/projects、~/.codex/sessions同左
相手のログのコピー~/ctx-mirror/minis/~/ctx-mirror/torrent-nano-wsl2/
Syncthing の設定~/.config/syncthing/~/.local/state/syncthing/
2026-09-05 以前の履歴ctx の中~/ctx-archive/torrent-nano-wsl2/

届くまでの時間 #

場面目安
ふだんの追記30秒ほど。ctx にもすぐ反映
初回のコピー(約830MB)約5分
片方の電源が切れていた両方が動いたときに追いつく

WSL2 と minis は、Tailscale の中継サーバー経由でつながっている。そのため大きな転送は遅いが、ふだんの追記なら困らない。

正常か確かめる #

相手のPCとつながっているか(どちらのPCでも同じ)

syncthing cli show connections | jq '.connections[] | select(.connected) | .address'

相手のアドレスが1行出れば正常。何も出ないときは、相手のPCの電源と Tailscale を確かめる。

ctx がコピーを読めているか

ctx sources

相手の分の取り込み元が available なら正常。名前は次のとおり。

PC相手の分の名前
WSL2minis-claude、minis-codex
ministorrent-nano-wsl2-claude、torrent-nano-wsl2-codex

画面で見たいときは、minis の本体のブラウザで http://127.0.0.1:8384 を開く。フォルダがすべて「最新」なら同期済み。

やってはいけないこと #

  • .stfolder を消さない。 ~/.claude/projects と ~/.codex/sessions の中にある、Syncthing の目印。消すと同期が止まる。
  • コピー先にある空のファイルを消さない。 ~/ctx-mirror/<相手>/codex/ の archived_sessions と history.jsonl のこと。無いと ctx がエラーを出す。
  • コピーを ~/.claude/projects に置かない。 Claude Code 自身の履歴と混ざり、30日で消される。

よく出る表示 #

ctx import --all を実行すると、どちらのPCでも「2件失敗」と出る。どちらも同期とは関係ない、もとからある問題なので、放っておいてよい。

  • サブエージェントのログが symlink になっていて、ctx が読まない
  • Codex のログ1件に2つのセッション情報が入っていて、ctx が保留にしている

ほかの履歴の取り込みには影響しない。

なぜこの方法にしたか #

ほかに3つの方法を比べた。日報づくりも含めて、両方のPCの分をふだんどおり扱えるのはこの方法だけだった。

方法結果
ctx の書き出しと復元(archive)一度きりの移行向き。別の置き場所になり、ふだんの検索に混ざらない。重ねて復元すると衝突する
ctx のサーバー機能(beta)別のPCから読めるのは検索と表示だけで、list events は読めない。版合わせも要る
rsync と Tailscale SSH自動で回すには SSH の再認証をやめる必要があり、端末ごとにも絞れない
Syncthing と ctx の追加の置き場所(採用)SSH を緩めずに済み、ふだんの ctx に混ざり、すぐ届く

付録:構築手順 #

作り直すときのメモ。WSL2 は Syncthing 1.18、minis は 1.27 で動かしている。

1. Syncthing を入れて起動する #

両方のPCで入れる。dotfiles の etc/packages.list にも追加してある。

sudo apt install -y syncthing

既定の共有フォルダを作らずに初期設定を作り、ログイン時に起動するようにする。

# WSL2(1.18)
syncthing serve --generate="$HOME/.config/syncthing" --no-default-folder
# minis(1.27)
syncthing generate --no-default-folder

# 両方
systemctl --user enable --now syncthing.service

minis は起動時に自動ログインする設定なので、再起動しても Syncthing が動く。

2. Tailscale の中だけで通信させる #

公開の探索サーバーと中継サーバーを使わない。クラッシュ報告と使用統計も送らない。

for o in global-ann-enabled local-ann-enabled relays-enabled natenabled crenabled; do
  syncthing cli config options "$o" set false
done
syncthing cli config options uraccepted set -- -1

3. 相手のPCとフォルダを登録する #

syncthing cli config devices add --addresses は、両方の版で異常終了した。そこで Syncthing の API で登録する。まとめて送ると400エラーになるので、1件ずつ送る。

# 設定ファイルは 1.18 なら ~/.config/syncthing、1.27 なら ~/.local/state/syncthing にある
K=$(sed -n 's@.*<apikey>\(.*\)</apikey>.*@\1@p' ~/.config/syncthing/config.xml)
post() {
  curl -fsS -X POST "http://127.0.0.1:8384$1" \
    -H "X-API-Key: $K" -H 'Content-Type: application/json' -d "$2"
}

# 自分のデバイスIDを調べる
syncthing cli show system | jq -r .myID

# 相手のPC。アドレスは Tailscale の名前で直接書く
post /rest/config/devices '{
  "deviceID": "<相手のID>",
  "name": "minis",
  "addresses": ["tcp://minis.<tailnet>.ts.net:22000"]
}'

# 自分のログは送るだけ
post /rest/config/folders '{
  "id": "ctx-claude-torrent-nano-wsl2",
  "path": "/home/<ユーザー>/.claude/projects",
  "type": "sendonly",
  "devices": [{"deviceID": "<自分のID>"}, {"deviceID": "<相手のID>"}]
}'

# 相手のログは受け取るだけで、消さない
post /rest/config/folders '{
  "id": "ctx-claude-minis",
  "path": "/home/<ユーザー>/ctx-mirror/minis/claude/projects",
  "type": "receiveonly",
  "ignoreDelete": true,
  "devices": [{"deviceID": "<自分のID>"}, {"deviceID": "<相手のID>"}]
}'

Codex も同じ形で登録する。フォルダ ID は両方のPCでそろえる。

フォルダ ID送る側受ける側
ctx-claude-torrent-nano-wsl2WSL2 の ~/.claude/projectsminis の ~/ctx-mirror/torrent-nano-wsl2/claude/projects
ctx-codex-torrent-nano-wsl2WSL2 の ~/.codex/sessionsminis の ~/ctx-mirror/torrent-nano-wsl2/codex/sessions
ctx-claude-minisminis の ~/.claude/projectsWSL2 の ~/ctx-mirror/minis/claude/projects
ctx-codex-minisminis の ~/.codex/sessionsWSL2 の ~/ctx-mirror/minis/codex/sessions

4. ctx にコピーを読ませる #

WSL2 での例。minis では minis を torrent-nano-wsl2 に読み替える。

# Codex は archived_sessions と history.jsonl が無いとエラーになるので、空のものを置く
mkdir -p ~/ctx-mirror/minis/codex/archived_sessions
touch ~/ctx-mirror/minis/codex/history.jsonl

ctx sources add minis-claude --provider claude --root ~/ctx-mirror/minis/claude --source-group minis
ctx sources add minis-codex  --provider codex  --root ~/ctx-mirror/minis/codex  --source-group minis

あとは ctx の常駐プロセスが自動で取り込む。

まだやっていないこと #

  • 2026-05-27〜08-11 の Claude Code の履歴は、旧版 ctx(0.19)のバックアップにしか無い
  • Claude Code のプロジェクトごとのメモリは、minis に移していない
  • Claude Code の30日削除は止めていない

関連 #