手動では読めるのにlaunchdでは「アクセス拒否」になる|Macのプライバシー権限の落とし穴

手動では読めるのにlaunchdでは「アクセス拒否」になる|Macのプライバシー権限の落とし穴 定期実行と通知

ターミナルで打てば普通に読めるファイルが、時間になったらMacが勝手に動かす仕組み(launchd)から呼ぶと「アクセス拒否」で止まる。これはパスの書き間違いではありません。パスは1文字も合っています。

結論を先に書きます。エラーに Operation not permitted と出ていたら、原因はmacOSのプライバシー保護です。そして直し方は、フルディスクアクセスを与えることではなく、ファイルを保護されていない場所へ移すことです。うちは実際にそうやって直しました。作業は設定1行の書き換えで済みました。

私は清掃業を経営していて、プログラマーではありません。この記事は、2026年8月27日の朝にうちの自動化が止まったときのログと、そのとき直した設定ファイルを読み返して書いています。


うちで起きたこと:朝9時に1行だけ残して止まった

うちのMacは毎朝9時に、記事を1本書かせる処理をlaunchdから動かしています。2026年8月27日、その処理が残したエラーログは、たった1行でした。

find: /Users/(ユーザー名)/Desktop/新規事業/G_メディア事業/記事/wordpress投稿用: Operation not permitted

日本語にすると「そのフォルダを見ることは許されていません」です。「そんなフォルダはありません」ではありません。フォルダはあります。私がターミナルを開いて同じことをすれば、中身は一覧で出ます。それでもlaunchdから動かしたときだけ、拒否されました。

作業の記録を見ると、その日は1本も記事ができていません。8月26日の記録の次が、いきなり8月28日になっています。丸1日ぶんが消えました。直したのは同じ日の10時49分。記事の置き場をデスクトップの中から ~/scripts/media/記事 に移し、置き場所を書いてある設定ファイルの1行を書き換えただけです。

いちばんたちが悪いのは「あるかどうか」は確かめられること

ここが、この不具合をわかりにくくしている最大の理由です。

うちのスクリプトには、動き出す前に置き場所を確かめる行が入っていました。

[ -d "$MEDIA_KIJI_DIR" ] || { echo "記事の置き場がありません: $MEDIA_KIJI_DIR"; exit 2; }

「そのフォルダはありますか」という確認です。当日のログを最後まで見ましたが、この「記事の置き場がありません」は一度も出ていません。つまりこの確認は通っています。通ったうえで、その直後に中身を数える処理が拒否されました。

macOSは、この2つを別の許可として扱います。

やろうとしたこと launchdから
そのフォルダが存在するか確かめる できる
そのフォルダの中身を一覧にする・ファイルを開く 拒否される

だから「ちゃんと存在チェックを入れてある」だけでは、この失敗は防げません。存在チェックは満点で通過します。

「アクセス拒否」は2種類ある。見分け方は2語だけ

ログに出る英語を、この2つだけ見分けてください。ここを間違えると、直らない方向を半日探すことになります。

ログの文字 意味 直し方
Permission denied ファイル自体の読み書きの設定が足りない chmod で直せる。よくあるのは実行権限の付け忘れ
Operation not permitted macOSのプライバシー保護にはじかれた chmod では絶対に直らない。置き場所か、許可設定の問題

Operation not permitted が出ているのに chmod 777 を試す、というのがいちばんありがちな遠回りです。ファイル側をいくらゆるくしても、macOSの保護は外れません。持ち主も権限も関係なく、はじかれます。

なぜ手で叩くと読めるのか

macOSは、デスクトップ・書類・ダウンロードの3つのフォルダ(ほかにiCloud Drive、外付けディスク、他のアプリのデータなど)を特別に守っています。ここに入るには、プログラムごとに許可が要ります。

ターミナルを自分で開いて打つときに読めるのは、ターミナルに許可が下りているからです。一度「デスクトップにアクセスしようとしています」と聞かれて「OK」を押した、あれです。

ところがlaunchdから動く処理は、ターミナルの中にはいません。まったく別の立場で起動されるので、ターミナルに出した許可は1ミリも効きません。「手動では動くのに自動だと動かない」の正体はこれです。

フルディスクアクセスを与える前に、必ず確かめる3つ

検索すると「フルディスクアクセスを与えれば直る」と出てきます。直ります。ただ、与える前に3つだけ確かめてください。与えるものが、思っているより広いことが多いからです。

1. そのファイル、動かせないか

まずこれです。データをデスクトップや書類から出せるなら、許可の話は一切要らなくなります。うちはこれで済みました。設定ファイルの1行を書き換えただけです。「与える」より「動かす」ほうが、たいてい速くて安全です。

2. 許可を与える相手は、自分のスクリプトではない

ここを勘違いすると危ないところです。launchdの設定ファイル(plist)は、こういう書き方をしています。

<key>ProgramArguments</key>
<array>
    <string>/bin/zsh</string>
    <string>/Users/(ユーザー名)/scripts/media/1本作る.sh</string>
</array>

実際に起動されているのは /bin/zsh(シェル)で、自分のスクリプトはそれに渡される材料にすぎません。だからフルディスクアクセスの一覧に入れることになるのは、自分の書いたスクリプト1本ではなく、シェルそのものです。

つまりそのMacで今後動くシェルスクリプト全部に、まとめて許可を出すことになります。自分が書いたもの、AIに書かせたもの、どこかからコピーしてきたもの、すべて含みます。

3. フルディスクアクセスは「フォルダ1つ」の許可ではない

名前のとおりです。困っているデスクトップの1フォルダだけでなく、メール、メッセージ、Safariの履歴、Time Machineのバックアップまで含めた、まるごとの許可になります。読むだけでなく、書き換えることもできます。

うちが与えなかった理由はこれです。うちはAIに毎日スクリプトを走らせていて、以前、権限を絞らないままAIに別の領域を読ませたら本番のスクリプトを勝手に書き換えられたことがあります。シェルに全許可を渡すと、その事故の届く範囲まで一緒に広がります。

移すときの手順(コピペで動く形)

うちが8月27日にやったのと同じ順番です。このMacにはgitを入れていないので、控えは tar で固めています。

# 1) 移す前に、まるごと控えを取る(フォルダ名は自分のものに置き換える)
mkdir -p ~/scripts/backup
cd ~/Desktop
tar czf ~/scripts/backup/移動前_$(date +%Y%m%d-%H%M%S).tar.gz "対象のフォルダ名"

# 2) 保護されていない場所へ移す
mkdir -p ~/scripts/データ
mv ~/Desktop/"対象のフォルダ名"/* ~/scripts/データ/

# 3) 場所を書いてある設定を直す(詳しくは下)

# 4) まず手で1回動かす
~/scripts/あなたのスクリプト.sh

# 5) launchdからも1回動かして、時刻を待たずに確かめる
launchctl kickstart -k gui/$(id -u)/あなたのラベル名

4番だけで終わらせないでください。手で動いたことは、launchdで動く保証になりません。今回の不具合はまさに、手では通ってlaunchdでだけ落ちるものでした。5番まで実行して、エラーログに何も増えないことを見てください。

3番の「設定を直す」が1行で済むかどうかは、置き場所を1か所にまとめてあるかで決まります。うちは設定ファイルにこう書いてあります。

# 記事HTMLが置いてあるフォルダ
MEDIA_KIJI_DIR="${MEDIA_KIJI_DIR:-$HOME/scripts/media/記事}"

ここが以前は $HOME/Desktop/… でした。この1行を書き換えるだけで引っ越しが終わりました。もし6本のスクリプトそれぞれにパスが直書きしてあったら、6か所を直すことになり、1つ直し忘れて別の失敗が始まっていたはずです。

そもそも保護されていない場所はどこか

迷ったら、次のどれかに置けば権限の話は起きません。うちはこう使い分けています。

  • スクリプトと作業用データ … ~/scripts/ の下に自分でフォルダを作る
  • 自動化が吐くログ … ~/scripts/…/logs/、または ~/Library/Logs/

ログの置き場所は特に大事です。launchdの設定にはエラーの書き出し先を指定する欄がありますが、その書き出し先まで保護フォルダの中にしてしまうと、エラーそのものが残りません。今回1行だけでも記録が残ったのは、ログの出力先が ~/scripts/…/logs/ だったからです。もしデスクトップに吐かせていたら、「何も起きなかった」ようにしか見えませんでした。

launchdから本当に読めるか、待たずに確かめる

ターミナルで ls して読めても意味がない、という話をしました。launchdと同じ立場で確かめるには、launchdに動かしてもらうしかありません。使い捨ての確認用ジョブを1つ作るのが確実です。

# 確認したいフォルダのパスを1行目で指定する
TARGET="$HOME/Desktop/確かめたいフォルダ"

cat > ~/Library/LaunchAgents/com.test.kengen.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>com.test.kengen</string>
  <key>ProgramArguments</key>
  <array><string>/bin/ls</string><string>$TARGET</string></array>
  <key>StandardOutPath</key><string>$HOME/scripts/kengen_test.log</string>
  <key>StandardErrorPath</key><string>$HOME/scripts/kengen_test.log</string>
</dict></plist>
EOF

launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.test.kengen.plist
launchctl kickstart -k gui/$(id -u)/com.test.kengen
sleep 2
cat ~/scripts/kengen_test.log

# 確かめ終わったら必ず片付ける
launchctl bootout gui/$(id -u)/com.test.kengen
rm ~/Library/LaunchAgents/com.test.kengen.plist ~/scripts/kengen_test.log

ログにファイル名が並べば、launchdからも読めています。Operation not permitted と出たら、それがこの記事の症状です。最後の2行の片付けを忘れないでください。

気づく仕組みも一緒に作る

今回いちばん痛かったのは、丸1日ぶんが消えたことそのものより、その日のうちに気づかなかったことです。個別の失敗を知らせる仕組みは入れてありましたが、「今日ぜんぶ動いたか」を1か所で見る場所がありませんでした。

いまは毎日決まった時刻に、その日の自動化が全部動いたかを1件ずつ確かめて、まとめて1通だけ通知する仕組みを別に動かしています。作った理由は、そのスクリプトの冒頭にこの日の日付付きで書き残してあります。失敗の理由をコードの先頭に日本語で書いておくと、半年後の自分が同じ失敗を繰り返しません。

まとめ

  • ログに Operation not permitted と出たら、パスは合っている。macOSのプライバシー保護が原因
  • Permission denied とは別物。chmod をいくらいじっても直らない
  • 「フォルダがあるか」の確認は通ってしまう。存在チェックを入れてあっても防げない
  • ターミナルで読めるのは、ターミナルに許可が下りているから。launchdには効かない
  • フルディスクアクセスを与える相手は自分のスクリプトではなく /bin/zsh。そのMacのシェルスクリプト全部に効く
  • まず「ファイルを動かせないか」を考える。デスクトップ・書類・ダウンロードから出すだけで解決することが多い
  • 置き場所は設定1か所にまとめておく。引っ越しが1行で終わる
  • ログの出力先だけは、絶対に保護フォルダの外に置く。そこが読めないとエラーすら残らない

手で動かして満足せず、launchctl kickstart でlaunchdからも1回動かす。この習慣だけで、この種の失敗はほぼ当日中に見つかります。

launchdが動かないときの原因はほかにもあります。終了コード127で止まる場合や、そもそも登録できていない場合については launchdが動かない・終了コード127の原因と対処 に書きました。

コメント

タイトルとURLをコピーしました