◉ANTIGRAVITY LABEN
記事一覧/AIツール
⚙ AIツール/2026-10-06中級

キャッシュを消すだけのはずが、ドライブごと消えた報告を読んで。削除だけは関所を通すことにしました

キャッシュ削除を頼んだらドライブの中身が消えた、という報告を読んだ夜、自分の削除スクリプトを開き直しました。承認を外す前に、削除の届く範囲だけを機械で狭める小さな Python の関所と、任せてよい作業・任せない作業の線引きをまとめます。

Antigravity377安全設計削除コマンドPython17承認設定

「キャッシュを消してほしい」と頼んだ結果、開発者のドライブの中身が消えてしまったのだと報じられていました。その報告を読んだ夜のことです。私は自分のビルド掃除用のシェルスクリプトを開き直しておりました。

個人開発のアプリでは、ビルドの残骸を消す作業が週に何度も発生します。その中に、変数が空のまま動けば根元のディレクトリを指しうる行が、一つ残っていました。報告の主役はエージェントでしたが、胃のあたりが冷えたのは、自分の古いスクリプトに対してでした。いま思えば、他人の事故を読んでいるつもりで、自分の棚卸しを迫られていたのかもしれません。

最初にお伝えしたいのは、承認を外してよいかどうかより先に、削除がどこまで届くかを機械で狭めておくほうが、はるかに効くということです。このあと、その関所を Python で一枚作り、任せてよい作業と任せない作業の線引きまでを書き残してまいります。

報じられたことと、私が断定しないこと

私が確認できたのは、報道の見出しと概要の範囲です。キャッシュの削除を頼んだところ、意図しない範囲まで消えた、という内容でした。原因が指示の曖昧さなのか、パスの解決なのか、権限の設定なのかは、公開されている範囲では私には判断がつきません。

ですので、ここから先は「あの事例の再現」ではなく、同じ種類の事故を自分の環境で起こさないための手当てとして読んでいただければと思います。

事故の種類は、突き詰めると一つです。削除コマンドの対象が、頼んだ人の想像より広かったのだと思います。それだけのことです。

「頼み方」を磨くより、「届く範囲」を先に狭めます

最初のうち、私は指示文のほうを直そうとしておりました。「このプロジェクトの build フォルダだけを」と書き添え、念押しを増やしていったのです。結果は芳しくありませんでした。念押しは、読む側が守ってくれたときにしか効きません。

いまは順番を逆にしています。対象が正しいかどうかを人の注意や文章の丁寧さに預けず、削除を実行する直前の一点で、機械が範囲を検査します。

検査する項目は多くありません。

  • 対象が空文字ではないこと(変数が展開されなかった場合を止めます)
  • 実体のパスに解決したとき、プロジェクトの内側にあること
  • プロジェクトのルートそのものではないこと
  • ルートや /Users/名前 のような浅い場所ではないこと
  • シンボリックリンクを辿らないこと
  • .git を含むディレクトリではないこと

どれも、事故のあとに読み返すと当たり前に見える項目です。当たり前だからこそ、毎回人の目に任せると抜けます。

関所の実装(既定は dry-run)

次のスクリプトを safe_rm.py として、プロジェクトの外側(たとえばホーム直下の bin)に置きます。プロジェクトの内側に置かないのが、地味ですが大事な点です。 エージェントが編集できる場所に置くと、関所そのものが書き換わりうるためです。

#!/usr/bin/env python3
"""削除系コマンドの手前に置く関所。既定は dry-run、--apply を付けたときだけ消す。"""
import argparse
import os
import shutil
from pathlib import Path
 
MIN_DEPTH = 3  # /Users/name/project より浅い場所は無条件で拒否
 
def check(target: str, root: Path) -> Path:
    if not target.strip():
        raise SystemExit("拒否: 対象が空です(変数が展開されていない可能性があります)")
    p = Path(target).expanduser()
    if p.is_symlink():
        raise SystemExit(f"拒否: シンボリックリンクは辿りません: {p}")
    real = p.resolve(strict=True)
    if len(real.parts) <= MIN_DEPTH:
        raise SystemExit(f"拒否: 浅すぎる場所です: {real}")
    if real == root or root not in real.parents:
        raise SystemExit(f"拒否: プロジェクトの外、または root そのものです: {real}")
    if (real / ".git").exists():
        raise SystemExit(f"拒否: .git を含むディレクトリは消しません: {real}")
    return real
 
def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("targets", nargs="*")
    ap.add_argument("--root", default=os.getcwd())
    ap.add_argument("--apply", action="store_true")
    a = ap.parse_args()
    root = Path(a.root).resolve(strict=True)
    if not a.targets:
        raise SystemExit("拒否: 対象がありません")
    for t in a.targets:
        real = check(t, root)
        if real.is_dir():
            size = sum(f.stat().st_size for f in real.rglob("*") if f.is_file())
        else:
            size = real.stat().st_size
        print(f"{'削除' if a.apply else '予定'}: {real} ({size:,} bytes)")
        if a.apply:
            shutil.rmtree(real) if real.is_dir() else real.unlink()
 
if __name__ == "__main__":
    main()

「なぜこう書くか」を三つだけ添えます。--apply を付けない限り何も消えないのは、エージェントが最初に打つコマンドを必ず「予定の表示」にするためです。resolve(strict=True) は、存在しないパスを黙って通さないためです。そして root not in real.parents で判定しているのは、文字列の前方一致だと /proj と /proj-old を取り違えるからです。

手元で確かめた結果

実際に、次の構成を作って動かしました。proj の中に build/cache と、外側を指すシンボリックリンクを置き、proj の外には消されては困るファイルを一つ置いています。

渡した対象結果
proj/build/cache(dry-run)予定の表示のみ。何も消えない
空文字拒否(終了コード 1)
proj の外のディレクトリ拒否
外側を指すシンボリックリンク拒否。外側のファイルは無傷
プロジェクトのルートそのもの拒否
/tmp(浅い場所)拒否
.git を含むサブディレクトリ拒否
proj/build/cache(--apply)削除。外側のファイルは残る

終了コードが 1 で返るので、エージェントが走らせたときも、失敗として表に出ます。黙って通り抜けることがありません。

承認を外す作業と、外さない作業

関所があっても、任せる範囲の線引きは別に要ります。私は次のように分けています。

作業承認なしで任せる理由
ビルド成果物の再生成任せる消えても同じ手順で戻せるため
safe_rm.py 経由の削除(dry-run)任せる予定の表示だけで、何も変わらないため
safe_rm.py --apply目で見てから表示された「予定」の一覧を、人が一度読みます
プロジェクト外のパスを含む削除任せない戻せるかどうかを、人が先に確かめます
同期フォルダ(Dropbox など)配下の削除任せない削除が他の端末へ伝わるため

同期フォルダの行は、私自身の実感から足しました。Dropbox の中で消したものは、手元のバックアップがなければ、版履歴に頼ることになります。復元できるとはいえ、夜中にそれを探す作業は、できれば避けたいものです。

関所を通さないときの、もう一つの保険

削除を機械で狭めても、戻せる経路は別に持っておきます。大きな作業の前には、次の二つだけを欠かさないようにしています。

  1. 作業ディレクトリを Git で一度コミットし、未追跡ファイルも含めて状態を固定する
  2. プロジェクトの外にあるものは、そもそもエージェントの作業ディレクトリに入れない

どちらも、手順としては短い文で済みます。短いからこそ、疲れている日でも飛ばしにくいのです。

承認の設定画面の名称や置き場所は版によって変わりますので、ここでは触れません。許可リスト・拒否リストの考え方そのものは、公式のドキュメントに整理されています。設定を触る日は、そちらを開きながら確かめていただければと思います。

まずは一行だけ

今日いちばんにお勧めしたいのは、手元の削除スクリプトを開いて、変数が空だったら何を消すのかを確かめることです。私は、それだけで一つ冷や汗をかきました。

頼み方は人が磨き、届く範囲は機械が狭めます。 この分担だけは、疲れた日でも崩さないようにしています。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⚙ AIツール2026-08-14
速いはずのモデルが遅いとき、疑うのはモデルではなく推論強度です
Antigravity でモデルごとに選べるようになった推論強度(Low / Medium / High)を、作業の性質からどう決めるかをまとめました。段階を変えても速くならないときに疑う箇所と、自分の判断を記録で直す方法も添えています。
⚙ AIツール2026-07-30
Pro に回す件数を半分にしても、トークンは6割残っていた — モデル振り分けの基準を測り直す
単数の設定が models コレクションに置き換わり、複数モデルのルーティングを自分で決める必要が出ました。タスク種別で振り分けていた基準を、手元のリポジトリ67タスクの実測分布で測り直した記録です。
⚙ AIツール2026-07-05
AI Ultra 100ドル枠は個人開発に見合うか — 上限の壁と並列度で損益分岐を測る
月100ドルの AI Ultra 枠(Pro の5倍上限)が個人開発者に見合うかを、上限に当たる頻度と並列エージェントの実効スループットから損益分岐として試算する方法を、計算スクリプトつきで整理します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます