Claude Code の ~/.claude が朝いちで丸ごと消えた — GitHubバックアップからの復元と、自動バックアップの二重化
朝、Claude Code の下に出るステータスバーが消えていた。表示だけの話だと思って、直前に打った /login を疑った。違った。原因はもっと下の層で、~/.claude が今朝まるごと作り直されていた。
VSCode で開いていた ~/.claude のツリーを覗くと、skills も commands も無い。ディレクトリ自体は存在するのに、中身が新品に入れ替わっている。壊された、という気持ち悪い直感の方が正しかった。
消えていたのは同期の本体だった
決定的だったのは ~/.claude/.git が無かったことだ。ここが GitHub との同期の本体で、スキル・コマンド・ルール・フック・settings.json・statusline は全部その中で git 管理下にあった。管理下ごと消えていた。
1時間前の push が残っていた
設定ディレクトリのバックアップ用リポジトリを確認させたら、最終コミットが 2026-07-29 06:35:41。消失のおよそ1時間前に push が入っていた。.gitignore を読むと、消えて困るものは全部 git 管理下に入っていた。方針は迷わなかった。.git ごと丸ごと戻す。
ところが復元コマンドが auto mode の分類器に止められた。~/.claude 配下への書き込みは、Claude Code 自身が自分の設定やフックを勝手に書き換えられないようガードされている。承認を出して進めさせた。
戻ったことは説明を待たずに分かった。応答が返る直前で、スキル一覧が画面に復活していた。
戻らなかったもの
セッションログは戻らない。~/.claude/projects/ ごと消えていて、しかも壊した本人のセッションログがその中にあった。今日残っていたのは 07:24 以降のログだけ、つまり事故より後のものだけだ。
/clear のときに自動アーカイブする仕組みを入れるべきかと考えたが、cleanupPeriodDays: 99999 で Claude Code 側の自動削除はオフになっていたので、/clear は今回の損失と関係がなかった。
被害は ~/.claude で止まっていなかった。ホーム直下を洗わせたら、~/.claude.json・~/.gitconfig・~/.config/ も消えていた。.ssh .npmrc .rustup は無傷。~/.config が飛んだせいで mise のグローバル設定と CLI 群の認証が落ち、node と pnpm がその場で動かなくなった。バイナリは残っていて、活性化する設定だけが消えた状態だった。
犯人は分からなかった
一番怪しかったのは、自動チェーンの中の「Step 14(ログ削除)」という工程だ。掃除をする場所は、ドットファイルを消しうる場所でもある。中身を読ませたら、cd してプロジェクト配下の明示パスしか消していない。claude-code-sync/2026-07-28 は狙い通りに消えていて、Step 14 は正常に完走していた。シロだ。
時刻を突き合わせて、犯行時刻は codex exec が走っていた 07:19:30〜07:23 の数分間まで絞れた。Windows のイベントログも見た。そこから先は詰められていない。壊した本人のログが道連れになっているので、肝心の証拠が残っていない。
実装の前に Codex に殴らせた
再発防止の方針は2本にした。
- セッションログを増分でアーカイブして、手元の外に出す
~/.claudeを private リポジトリに自動同期する
両方やる前に、妥当性を Codex でレビューさせた。ここで一度足を止めることになった。Codex 自身の認証も ~/.config の巻き添えで飛んでいて、レビューを走らせるのに再ログインが要ったのだ。バックアップを作るためにバックアップが要る、という順序になっていた。
返ってきた結論は「このまま実装に入るな」。致命的な指摘が4件、論点が7つ。しかも過去の自分の事故(tar のオプション解釈で落ちた issue)を引いてきて、それが実在した。捏造ではなかった。
暗号化は age を使うつもりだったが、scoop の main バケットに無かった。Git for Windows 同梱の gpg 2.4.7(AES256 の対称暗号)で代替し、パスフレーズは 1Password に置いた。ラウンドトリップを確認してから、計画書を v2 に書き直させた。
① セッションログの暗号化アーカイブ
- 増分の基準は cutoff ファイル。送出が失敗したら cutoff を前進させない(次回また同じ範囲を送る)
- gpg で固めて、Google Drive の API 直アップロードと外付けドライブの2系統に送る
- SHA-256 のマニフェストを取る
保存先は最初 G:\(Drive for Desktop)に置く案だった。あれはキャッシュが C: に載るので、C: を空けたい目的と衝突する。API 直アップロードに変えて、C: には一時ファイルしか置かない形にした。
障害試験もやらせた。Drive を落とした状態で走らせたら、cutoff は前進しなかった。設計通りだ。復元テストは Drive から落として復号・展開し、19/19 ファイルが SHA-256 一致で戻った。増分も効いた(19 → 7 ファイル)。
途中で小石を2つ踏んだ。$TEMP が Windows 形式のパスなので、GNU tar が C: をリモートホストと解釈して落ちた(POSIX パスに直した)。もう1つは Drive 側の CLI で、アップロード対象のパスをカレントディレクトリ配下に制限していた。
② ~/.claude を private リポジトリへ
バックアップ用のリポジトリを private で新規に作って push した。中身は4ファイルだけで、状態ファイルは gitignore の通り入っていない。
ガードは2つ入れた。秘密検出(見つけたら exit 1 で止める)と、大量削除ガード。両方が本当に効くか検証させたら、検出は効いた。ただし検出のログに token の先頭40文字が出ていた。検出器そのものが漏出源になっていたので、そこを潰した。
復元作業の副作用も1つあった。git checkout -- . で settings.json まで巻き戻して、モデル設定が前の値に戻っていた。今朝設定した値に直した。
/Create が C:/Program Files/Git/Create に化ける
手で走らせる仕組みは、忙しい日に飛ぶ。schtasks に XML 定義ファイルを食わせる形で登録することにした(Codex が求めた「登録内容を再現できる設定ファイル」も兼ねる)。
手元で打ったコマンドが MSYS のパス変換に食われた。
schtasks /Create /XML "...\ClaudeBackup-SyncDotclaude.xml" /TN ... /F
→ エラー: 無効な引数またはオプションです - 'C:/Program Files/Git/Create'
/Create がパスと解釈されて C:/Program Files/Git/Create に化けている。変換を止めて叩き直させても、今度は「アクセスが拒否されました」。タスクのルートフォルダへの登録に昇格が要る、と読んだ。
PowerShell を -Verb RunAs で起こしたが、タスクは入らなかった。昇格ウィンドウのプロセスだけが残っている。XML が悪いに違いない、と思って検証させたら、2本とも正常で bash.exe のパスも実在していた。XML は原因ではなかった。
効いていたのは /XML 取り込みという経路の方だった。権限要求の軽い簡易形式に落として叩いたら、昇格なしで2本とも登録できた。取りこぼし(PC が落ちていた回の追いつき)の設定は Set-ScheduledTask が要って、これも昇格で弾かれた。追いつきをあきらめて、4時間ごとに複数回試行する形に置き換えた。
09:16:03、タスクスケジューラ経由で両方が起動した。sync は終了コード 0、sessions は増分8ファイルを処理して完走。パスフレーズの op read がタスクスケジューラ経由でも通ることまで確認できた。
mise の shim が残骸として証言した
前の作業で pre-commit hook が失敗していた。mise の node shim が壊れたと見て、node の実体を PATH に通して回避してあった。調べさせたら、shim は壊れていなかった。mise に「どのバージョンを使うか」を教える設定ファイルが1つも無い状態だった。~/.config の消失がここに出ていた。
復元後にもう1つ出た。wrangler が mise のシム経由で解決されて落ちる。戻した mise 設定は4ツール分だけで、シムには ccusage と wrangler が残っていた。設定が消えてもシムは残るので、そこから「元は何を入れていたか」を逆算できた。落ちていた理由は node のバージョン選択ミスで、グローバルパッケージは全部 22.22.2 側にあった。選び直したら wrangler 4.92.0 と ccusage 20.0.14 が復活した。
結局 ~/.config を使っていたのは mise / gws / op / scoop の4つだけで、全部戻っていた。wrangler の設定は %APPDATA% 側、gh も gcloud も %APPDATA% 側にあり、巻き添えを免れていた。
「ローカルの .env を消したら、どうやって読むのか」
ローカルに置いておくのが心細くなったので、半年以内にコミットのあるリポジトリの .env を10本、1Password に上げさせた。op document create <file> はパスを渡すだけなので、中身を一度も読ませずに済む。保管庫は途中で分けて、アイテムはドキュメントとして格納した。
このあたりで 1Password の認証プロンプトが何度も出て、うっとうしくなった。値を出さずにサイズだけ照合する、という提案も切った。中身が取れないなら照合の意味が薄い。それで疑問がはっきりした。
ローカルの .env を消したら、1Password に入れた控えから、アプリはどうやって読むのか。
答えは、読めない。1Password は消えたときの復旧元であって、実行時の供給源ではない。.env はアプリが起動時にディスクから読むものなので、ファイルが無ければ動かない。今日やったのは金庫に控えを置いたところまでで、読み込み方は何も変わっていない。.env はローカルに置いたまま使う。
ここでも小石を踏んだ。op://Private/... で読めず、保管庫の内部名が Personal だった(アプリの表示は「個人」)。保管庫を分けたら名前に空白が入って、参照が op://Dev と Secrets/... に割れて壊れた。リネームにも強い UUID 指定に変えた。アップロード5本が NG と報告されたのも誤りで、レスポンスのキーが id ではなく uuid だっただけで、実際は成功していた。
拾ったこと
- 助かった理由は1つだけだった。消失の1時間前に push が残っていたこと。バックアップは押した瞬間ではなく、押していた習慣が効く
- git 管理下に入れていたものは全部戻り、入れていなかったセッションログは戻らなかった。管理下の境界が、そのまま被害の境界になった
- 原因を調べる材料が、壊れた場所と同じ箱に入っていた。ログを手元だけに置かない理由がこれ
- 1Password に入れても、実行時にファイルが要るものは要る。復旧元と供給源を混ぜて考えていた
- シムやキャッシュの残骸は、消えた設定の証言者になる。何が入っていたかを思い出す手がかりに使える
犯人はまだ分かっていない。次に同じことが起きたら、消えた側ではなく、外に出したアーカイブの側から辿ることになる。