こんにちは、エンジニアのオオバです。
Mac mini の dotfiles を origin/main に揃えようとしたら、.gitconfig に +141行 のローカル差分が出てきました。
自分では1行も書いていません。
「え、誰が書いたの?」って一瞬ゾッとしますよね。
犯人は、同じ Mac mini で動かしていた GitHub Actions の self-hosted ランナーでした。
~/.gitconfigがsymlinkだとactions/checkoutの「一時HOMEへのコピー」もリンクのままになり、safe.directoryの追記がdotfiles本体に届く。set-safe-directory: falseで止まる
先に結論をまとめます。
①actions/checkoutは~/.gitconfigを一時HOMEにコピーしてから、そこへ--globalでsafe.directoryを書く
②コピーはsymlinkをリンクのまま複製するので、追記がdotfilesの.gitconfigに届く
③ランナーと作業ディレクトリの所有者が同じなら、set-safe-directory: falseにすれば追記が止まる
ざっくり言うと、前半が症状と原因、後半が直し方とその判断です。
では本編に入っていきます。
→11万文字で徹底解説した「DOTweenの教科書」Unityアニメーションの超効率化ツールはこちら
起きたこと:同じ1行が140回重複していた
差分の中身は、全部同じ1行でした。
safe.directory = /Users/ohbashunsuke/workspace/actions-runner/mf-getter/_work/mf-getter/mf-getter
この行が 140回 重複して、.gitconfig の末尾に積み上がっていたわけです。
パスを見れば、self-hosted ランナーの作業ディレクトリだとすぐ分かります。
放っておくと定期同期で衝突する
僕の Mac mini では、dotfiles-sync という launchd のジョブが毎日12:00に動いています。
ローカル変更を stash して、fast-forward して、stash を戻す流れです。
そのため、origin 側で .gitconfig の末尾を変えた日に stash を戻すと衝突し、#mm-alert(アラート用の Slack チャンネル)が鳴ります。
つまり、ランナーが書き足すたびに地雷を1個ずつ埋めていたということ。
環境
- macOS の Mac mini
- GitHub Actions self-hosted runner 2.334〜2.336(launchd で常駐、自分のユーザーで動かしている)
- ワークフローは
actions/checkout@v4(runs-on: self-hosted)を毎日2本 ~/.gitconfigは dotfiles リポジトリへの symlink(~/.gitconfig -> ~/workspace/dotfiles/.gitconfig)
最後の1行が今回の主役です。
原因:一時HOMEへのコピーがsymlinkのままだった
checkout の実行ログを見ると、次の4行が出ていました。
Copying '/Users/ohbashunsuke/.gitconfig' to '.../_work/_temp/<uuid>/.gitconfig'
Temporarily overriding HOME='.../_work/_temp/<uuid>' before making global git config changes
Adding repository directory to the temporary git global config as a safe directory
[command]/usr/bin/git config --global --add safe.directory .../_work/mf-getter/mf-getter
ログの流れはこうです。
~/.gitconfigを一時ディレクトリにコピーするHOMEをその一時ディレクトリに差し替えるgit config --global --add safe.directoryで作業ディレクトリを登録する
checkout は、本物の ~/.gitconfig を汚さないように作られています。
一時 HOME にコピーして、そちらに --global で書く。
設計としては丁寧なんですよね。
コピーがリンクをリンクのまま複製していた
ところが、このコピー(@actions/io の cp)は symlink をリンクのまま複製します。
僕の ~/.gitconfig は dotfiles を指す symlink です。
そのため、一時 HOME に置かれた .gitconfig も dotfiles を指すリンクになっていました。
「一時」のはずの追記が、リンクをたどって dotfiles 本体に書き込まれていたわけです。
身近な物で例えると、書類のコピーを取ったつもりが、実は原本の置き場所を書いた付箋をコピーしていた状態。
付箋のコピーに書き込もうとすれば、結局は原本に書き込むことになります。
後片付けでは行が消えない
checkout の後片付けは、一時ディレクトリを消すだけです。
一時ディレクトリにあるのはリンクなので、消えるのはリンクだけ。
dotfiles に追記された行はそのまま残ります。
ワークフローは毎日2本動いていたので、実行のたびに1行ずつ増えて140行になったということです。
解決:set-safe-directory: falseを指定する
checkout に set-safe-directory: false を渡しました。
- name: Checkout
uses: actions/checkout@v4
with:
set-safe-directory: false
「safe.directory を切って大丈夫なの?」と思うかも知れません。
結論、僕の環境では要りませんでした。
git が safe.directory を求めるのは、リポジトリの所有者と git を実行するユーザーが違うときだけです。
ランナーは作業ディレクトリの所有者と同じユーザーで動いているので、もともと git に怒られる状況になりません。
動作確認
ブランチ上で workflow_dispatch から実行し、次の3点を確かめました。
- ジョブは success
- checkout のログから safe.directory の追記が消えた
- dotfiles の
.gitconfigは差分なしのまま
ちなみに dotfiles に溜まっていた140行は、ランナー側を直しても消えません。
origin/main に揃えるときに捨てる必要があります。
他の選択肢と、選んだ理由
直し方は他にも2つ考えました。
- dotfiles の
.gitconfigを symlink で配るのをやめて、include で読み込む形にする - ランナーの
HOMEを別のディレクトリにする
どちらも効くと思いますが、ランナー以外のツールや普段の作業にも影響が出ます。
checkout の設定1行なら、影響はこのワークフローの中だけで済みます。
なので、いちばん範囲が小さい方法を選びました。
オチ:ランナーごと止めた
直して PR もマージしたあとで、mf-getter 自体をもう使っていないことに気づきました(笑)。
結局、同じ日にランナーごと停止しています。
とはいえ、原因の仕組みは他のツールでも起こり得る話なので、記録として残しておきます。
補足:symlinkでdotfilesを配っている人は要注意
今回の学びは「一時 HOME にコピーするから安全」という設計も、元ファイルが symlink だと素通りになるということ。
dotfiles を symlink で配っていると、CI や自動化ツールが ~/.gitconfig に --global で書いた内容は、そのまま dotfiles の差分になります。
self-hosted ランナーを普段使いの Mac で動かしているなら、一度 dotfiles の git status を見てみてください。
身に覚えのない行が増えていたら、今回と同じ仕組みを疑うのが近道です。
まとめ
①~/.gitconfigがsymlinkだと、actions/checkoutの一時HOMEへのコピーもリンクのままになる
②その結果、safe.directoryの追記がdotfilesの.gitconfigに実行のたびに1行ずつ溜まる
③ランナーと作業ディレクトリの所有者が同じなら、set-safe-directory: falseで止められる
自分で書いた覚えのない差分って、見つけた瞬間かなり焦りますよね。
ただ、ログを順番に追えば原因はちゃんと書いてあります。
同じ症状で困っている人の近道になればうれしいです。

この記事が気に入ったらフォローしよう
