同じビルドスクリプトがSSHターミナルでは正常に動作するのに、クラウドMac CIへ移すと Permission denied になる場合、最もありがちな誤解は「もう一度 chmod +x を実行すれば直る」というものです。実際の原因は、多くの場合、ジョブ開始時の umask、Gitインデックスに保存された実行可能ビット、コピーおよび展開ツールの権限処理、そしてスクリプトが実際に使用するインタープリターという4つの層にまたがっています。作業ツリー内のファイルを1つ修正するだけでは、次回のクリーンチェックアウトで同じ問題が再発します。
まず権限問題を4種類に分類する
調査を始める前に、失敗した対象と操作の種類を記録します。スクリプトを実行できない、ディレクトリへ書き込めない、鍵ファイルの権限が広すぎる、アーカイブ展開後にモードが変わる、といった問題では、それぞれ対処方法がまったく異なります。
stat を使い、8進数のモード、所有者、ファイル種別をまとめて確認します。
target="${1:?path required}"
stat -f 'mode=%Sp octal=%OLp owner=%Su group=%Sg type=%HT path=%N' "$target"
ls -ldeO@ "$target"
2つ目のコマンドでは、ACL、ファイルフラグ、拡張属性も確認できます。通常の権限に問題がないように見えても失敗する場合は、すぐに権限昇格を試してはいけません。まず、親ディレクトリを辿れるか、ファイルが置かれているボリュームで実行が許可されているか、プロセスがどのアカウントで動作しているかを確認します。
rwxだけが判断基準ではありません。パスを構成する各階層のディレクトリにも適切な権限が必要であり、ACLが通常のモードビットより優先されることもあります。sudoを使い続けると、作業ツリー内で所有者が混在し、後続ジョブの再現性がさらに低下します。
最小限の現場情報を記録する
少なくとも id、umask、現在のディレクトリ、対象ファイルの stat 結果、マウント情報を保存します。環境変数をすべてログへ出力してはいけません。トークンが含まれている可能性があります。記録する情報は、認証情報の内容ではなく、実行主体、パス、権限に絞ります。
ジョブ開始時にumaskを固定する
umask が影響するのは新しく作成されるオブジェクトだけであり、既存ファイルの権限を書き換えることはありません。一般的な基準値である 022 では、通常ファイルはデフォルトで 644、ディレクトリは 755 になります。管理された同一グループの共同作業ディレクトリでは 002 も検討できますが、汎用的な修正として使用すべきではありません。ジョブ開始時に値を明示し、新規作成したプローブで検証します。
set -euo pipefail
umask 022
probe_dir="$(mktemp -d)"
probe_file="$probe_dir/probe"
: > "$probe_file"
file_mode="$(stat -f '%OLp' "$probe_file")"
dir_mode="$(stat -f '%OLp' "$probe_dir")"
test "$file_mode" = "644"
test "$dir_mode" = "700"
rm -rf "$probe_dir"
mktemp -d は一時データを保護するため、通常はモード 700 のディレクトリを作成します。そのため、通常のディレクトリが 755 になることの検証には使用できません。ディレクトリの基準モードをテストする場合は、既知の親ディレクトリ内で mkdir を実行し、別途確認します。この違いは、権限テストで誤検知が頻発する原因の1つです。
Gitに正しい実行意図を保存させる
Gitが主に追跡するのは、ファイルが実行可能かどうかであり、すべてのUnix権限を完全に保存するわけではありません。あるマシン上でスクリプトのモードを手動で 755 に変更しても、その情報がGitインデックスに記録されたとは限りません。作業ツリーだけでなく、インデックスを確認します。
git ls-files --stage |
awk '$1 == "100755" {print $4}' |
sort
実行可能ビットをコミットする必要がある場合は、次のコマンドを使用します。
git update-index --chmod=+x scripts/build.sh
git diff --summary
git diff --cached --summary
反対に、設定ファイルやドキュメントへ誤って実行可能ビットが付いている場合は、git update-index --chmod=-x で修正します。.sh ファイルでは先頭行も確認が必要です。#!/bin/zsh や #!/usr/bin/env bash など、実行環境に確実に存在するインタープリターを指定し、CRLFの改行コードが混入しないようにします。
マージ前のゲートでスクリプトを検証する
実行可能であるべきスクリプトの明示的な一覧を管理し、Gitインデックスの結果と比較できます。「すべての .sh ファイルを実行可能にする」といった単純な規則は避けてください。source で読み込むライブラリファイルには、実行可能ビットが不要な場合があるためです。規則は拡張子による推測ではなく、ファイルの用途を表すものにします。
コピー、圧縮、展開時の権限処理を管理する
cp、ditto、rsync、各種アーカイブ形式では、モードビット、ACL、拡張属性の扱いがそれぞれ異なります。ビルドキャッシュには再生成可能なデータだけを保存し、配布用パッケージは展開後に改めて権限を検証する必要があります。
作業ツリーをコピーする際は、最初にメタデータを保持する必要があるかを判断します。ソースファイルの内容だけが必要なら、古い所有者、ACL、拡張属性を意図せず引き継がないようにします。実行可能ビットを保持する必要がある場合は、一時ディレクトリ内で往復テストを行います。
src="scripts/build.sh"
tmp="$(mktemp -d)"
ditto "$src" "$tmp/build.sh"
before="$(stat -f '%OLp' "$src")"
after="$(stat -f '%OLp' "$tmp/build.sh")"
test "$before" = "$after"
rm -rf "$tmp"
アーカイブの受け入れ検証では、ファイルが存在するかどうかだけを確認してはいけません。少なくとも、エントリースクリプトが実行可能であること、通常の設定ファイルが実行可能ではないこと、機密ファイルをグループやその他のユーザーが読み取れないことを検証します。展開先には新しいディレクトリを使用し、既存ファイルのモードによってアーカイブ自体の問題が隠れないようにします。
修正を実行可能なポリシーにする
安定した解決策は、障害発生後に chmod -R 777 を一括実行することではありません。少数の明確で説明可能なアサーションを定義することです。次のチェックでは、他のユーザーが書き込めるファイルを拒否し、エントリースクリプトが実行可能であることを確認します。
set -euo pipefail
entry="scripts/build.sh"
test -f "$entry"
test -x "$entry"
while IFS= read -r -d '' file; do
mode="$(stat -f '%OLp' "$file")"
other_write=$((8#$mode & 2))
if (( other_write != 0 )); then
printf 'world-writable file: %s mode=%s\n' "$file" "$mode" >&2
exit 1
fi
done < <(find . -type f -not -path './.git/*' -print0)
ポリシースクリプトはリポジトリ内に置き、ローカルの事前チェックとCIで共用します。失敗時の出力は、パス、期待するモード、実際のモードだけに限定します。これにより、内容を漏えいさせずに問題を特定できます。RentMiniの専有物理ノードでジョブを実行する場合も、前回の環境が維持されていると仮定せず、新しい作業ツリーごとにこれらのアサーションを実行します。
最後に、クリーンチェックアウトで受け入れテストを行います。一時作業ツリーを削除してリポジトリを再クローンし、手動修正を一切行わずに、権限チェックと最小ビルドを直接実行します。この手順が成功して初めて、権限修正が一時的なリモートセッション内ではなく、バージョン管理へ正しく取り込まれたと判断できます。
よくある質問
chmod +xを実行してもCIでPermission deniedになるのはなぜですか?
実際に実行されるファイルへchmodしたか、ボリュームが実行を許可するか、shebangのインタープリターが存在するか、後続のコピーや展開で実行ビットが失われていないかを確認します。
クラウドMac CIではどのumaskを使うべきですか?
単一ユーザーのビルドでは022が現実的な基準です。管理されたグループ共有領域に限って002を検討し、設定値だけでなく生成されたファイルのモードを検証します。
権限ずれをマージ前に止める方法はありますか?
Gitインデックスの実行ビット、shebang、機密ファイルのモード、展開後の成果物を検査し、規約違反があればCIを非ゼロ終了させます。
次回のビルドに備えて専用物理Mac miniを準備
M4の構成、利用期間、ノードを選択します。各注文には専用の物理デバイスが割り当てられ、実際の利用可能状況はコンソールにリアルタイムで表示されます。