Macへの移行でバックアップを作り直したら、削除ガードが動いていなかった
Macへの移行でバックアップを作り直したら、削除ガードが動いていなかった
朝の日記コマンドの出力に、見慣れないスタックトレースが1行だけ混じっていた。
記事の生成は止まらず、最後まで通った。 だから最初は気に留めなかった。 別の記事を並列で書かせている間に、そのエラーだけ調べさせた。
無害ではなかった。
朝のログに混じっていたエラー
Nodeがモジュールの解決に失敗したときのエラーだった。
設定ファイルには、削除コマンドを実行前に検査するフックのパスが書いてある。 そのパスが旧マシンのままで、指している実体はこちらのマシンに無かった。 フックの本体を置いてあるリポジトリごと、まだクローンしていなかった。
読み込みに失敗したフックは、拒否も許可も返さない。 拒否の判断が返らなければ、コマンドはそのまま実行される。 再帰削除を止める仕組みが、止める側だけ黙って外れていた。
同じ設定ファイルには、シェルコマンドを分類器に通すための禁止文言もある。 そちらも旧マシンのパスで書いてあり、文字列が一致しないので実質的に効いていない。
移行してからの数日、その状態で動いていたことになる。
保存先を1つに減らす
バックアップ一式をMacへ移してほしい、と頼んだ。 ただし今回は保存先が1つしかない。
Windows側では、暗号化したセッション履歴をクラウドストレージと外付けSSDの2か所へ送っていた。 SSDは持ってきていないので、クラウド側だけにしてほしいと伝えた。
2系統を前提にした箇所が2つ引っかかった。 1つは外付け側への送出そのもの。 もう1つは、どこまで送ったかを記録する目印の前進判定だった。
元の設計では、片方でも失敗したら目印を前に進めない。 次の実行で同じ範囲を送り直すための仕掛けだ。 ただし系統が1つになると、無効な系統の失敗を永久に待つことになる。 有効な系統だけを見て判定するように変えさせた。
Windows側のノートPCも同じリポジトリを使う。 上書きはせず、OSを見て分岐させる方針にした。
クラウド側にはデスクトップ同期のフォルダが無いので、API経由で送る形になった。
bashが全角の括弧を変数名として食う
移植で最初に転んだのは、シェルのバージョン差だった。
macOSに最初から入っているbashは3.2で、Windows側で使っていた4系とは挙動が違う。 暗号化も送出もせず、収集と完全性検証までを流すDRY_RUNで走らせたら、変数が空になる箇所が出た。 原因は日本語のログ出力だった。
# 3.2 は「MANIFEST(...」までを変数名として読む
echo "$MANIFEST(..."
# 直した形
echo "${MANIFEST}(..."
全角の丸括弧が変数名の直後に来ると、3.2はそれを変数名の一部として読む。 存在しない名前の変数を探しにいって、空を返す。
全スクリプトを走査させたら12箇所あった。 すべて波括弧で囲う形に直した。 Windows側の4系でも意味は変わらないので、片方だけ動く直し方にはならない。
ここでDRY_RUNが通り、59ファイル127MBの収録一覧が完全に一致した。
鮮度をチェックするスクリプトがGNUの date を前提にしていた箇所も、BSDの date で通るよう直した。
残りは本番で走らせてから出てきた。
同じ3.2でもう1つ引っかかる。
空の配列を "${arr[@]}" で展開すると、set -u が未定義として弾く。
こちらは env を挟む形に変えた。
パスフレーズが渡らない
暗号化のところでも止まった。
gpg 2.x は --passphrase-fd でパスフレーズを渡すとき、--pinentry-mode loopback を併記しないと受け取らない。
Windows側ではagentの設定で通っていたらしく、この指定が無いまま動いていた。
両方のOSで有効な形に直させた。
テストコマンドが自分のガードに弾かれた
削除ガードのフックも、Windows版を壊さないようOS判定で分岐させた。
判定が正しく効くかどうかは、実際にJSONを流して確かめさせた。 許可すべきものと拒否すべきものを9通り流して、すべて期待どおりだった。
設定ファイルは書き換える前に退避を取らせた。 フック2件を両OS対応に書き換え、失敗したときに止まる性質はそのまま残した。 分類器に渡す禁止文言にはこちらのマシンのパス基準を足し、Windows側の記述は消していない。
書き換えたあとで動作確認のコマンドを流したら、そのコマンド自体が拒否された。
文字列に ~/.claude の削除が含まれていたからだった。
確認したかったことが、確認の失敗という形で返ってきた。
復元できて初めてバックアップになる
本番で走らせた。
127MBがクラウドへ上がり、サイズが一致し、目印も前に進んだ。 保存先の最新が8月1日のままだったのが、この日のものに入れ替わった。 8月1日は、Windows側で最後に走った日だ。
ここで止めれば「バックアップが動いた」で終わる。 戻せるかどうかは別の話なので、復元テストも移植させた。 クラウドから実際に落として復号し、展開した。 60ファイル全部のSHA-256を突き合わせたら、全件一致した。
日次の自動実行はlaunchdに登録した。 懸念はここだった。 バックアップはパスワードマネージャからパスフレーズを取り、クラウドへの送出にも認証が要る。 どちらも普段はターミナルから叩いていて、launchdが渡す環境はそれとは違う。
実際に起動させたら exit 0 で通り、パスフレーズの取得もアップロードも抜けた。
復元テストのほうは10日周期で登録した。 セッション履歴の自動削除は無効にしてあり、6月19日分がまだ残っている。 消える心配は当面ないが、壊れていることに早く気づける周期のほうがいい。
消し忘れた登録
設定ファイルを開いていて、セッション開始時に走るスクリプトの登録が目に入った。
拡張子が .ps1 で、パスがWindowsのものだった。
Macでしか使わないのに、なぜPowerShellなのか分からなかった。
タイムスタンプが答えだった。
.ps1 は8月2日の08:39、同じ処理のshell版は09:15。
移行の初日にPowerShell版を先に書いて登録し、36分後にshell版へ作り直している。
そのとき、設定ファイルから古いほうの登録を消していなかった。
残骸なので消させた。 設定に名前だけが残って実体が無いのは、削除ガードと同じ形だ。
許可リストにも同じものが溜まっていた。 Windows固有のエントリが22件。 全部消すとノートPC側で許可が外れるので、危ないものだけを3件外して、macOS用を足した。
あるはずのコマンドが見つからない
同じ朝、支援先の案件を扱うリポジトリで作業していて、日記コマンドが候補に出てこなかった。
ユーザーレベルに置いてあるつもりでいたので、消えたのかと思って調べさせた。
消えていなかった。 そのコマンドはユーザーレベルではなく、このサイトのリポジトリのプロジェクトレベルに置いてある。 別のリポジトリで作業していたのだから、候補に出る対象ではない。
先月末に環境を丸ごと失っている。 だから見つからないものを見ると、まず消えたと疑う。 今回は、置き場所を間違えて覚えていただけだった。
日記コマンド自体にも手を入れた。 月次のデータベースバックアップを回すステップがWindows専用の資材に依存していて、Macでは実行できない。 その日は手で飛ばしたが、手順定義に「macOSではスキップ」と書かせた。
ステータスラインの数字が空だった
ステータスラインに、5時間と1週間、それに上位モデルの利用制限のパーセンテージを出している。
Macに移してから、この3つが出ていなかった。 WindowsとMacの互換性だろうと見当をつけて、調査と修正を頼んだ。
そのとおりだった。 違っていたのは認証情報の置き場所で、MacではOAuthのトークンがKeychainに入る。 スクリプトはWindowsのファイルだけを見ていた。 だから取得が丸ごと失敗して、3つとも空欄になっていた。
Keychainから無人で取り出せることを、中身は出さずキー名だけ見て確認させた。 そのうえでKeychain経由の取得を足した。 実際に走らせて、3つのパーセンテージが並ぶところまで見た。
移行で壊れたものは、壊れたと言ってこない
この日直した3つは、症状の出方が違うだけで形は同じだった。
削除ガードはスタックトレースを1行出した。 セッション開始のスクリプトは何も出さず、設定ファイルに名前だけ残した。 ステータスラインは、数字のあった場所が空欄になった。
質が悪いのは、エラーを出さないほうだ。 バックアップが動いていることは、送出のログを見れば分かる。 ガードが効いていないことは、消されるまで分からない。
だから復元テストを周期で回すことにしたし、フックは9通りのJSONを流して確かめた。 「設定に書いてある」と「動いている」は別のことだと、移行のたびに教わっている。