ANTIGRAVITY LABEN
記事一覧/Antigravity 基本
Antigravity 基本/2026-08-20中級

CLI 1.1.14 に上げる前に、ワークスペースの外へ書いている箇所を数える

Antigravity CLI 1.1.14 で、ワークスペース外のパスは既定で読み取りのみになりました。素材フォルダと配信先をリポジトリの外に置いている構成では、どこが止まるのかを更新前に知っておきたいところです。書き込み先を機械で洗い出すスクリプトと、その数え漏れの直し方をまとめました。

Antigravity CLI27権限設計9ワークスペース4壁紙アプリ5運用設計27

プレミアム記事

壁紙アプリの素材は、アプリのリポジトリの中にありません。

元画像はクラウド同期のフォルダに置いてあり、派生画像を書き出す先はまた別の場所、配信用のステージングはさらに別の場所です。個人開発で6本のアプリを並行して面倒を見ていると、画像だけをアプリのリポジトリから切り離しておくのが一番楽でした。リポジトリを軽く保てますし、同じ素材を複数のアプリから参照できます。

その構成のまま、素材の仕分けや派生画像の書き出しをエージェントに任せてきました。エージェントから見ると、読む場所も書く場所もワークスペースの外側にあります。

Antigravity CLI 1.1.14 の変更点を読んでいて、手が止まったのはここでした。ワークスペース外のパスへのアクセスは、既定で読み取りのみになります。書き込みは実行モードに応じた認可が必要です。

つまり私の構成では、素材を読むところまでは通り、書き出すところで止まる可能性があります。しかも止まるのは、次にそのタスクが走ったときです。

更新してから気づくと、気づくのが遅い

この手の変更で厄介なのは、壊れ方が静かなことです。

エディタの前に座っているときなら、承認ダイアログが出て「あ、そうか」と分かります。困るのは、夜間に走らせているバッチや、CI から呼んでいる非対話実行のほうです。非対話でターン境界を守る設計については非対話実行でターン境界を守るでも触れましたが、あのとき前提にしていたのは「許可されている操作は通る」ことでした。既定が変わるなら、その前提から確認し直す必要があります。

そこで、更新の前に一度だけやっておく作業を決めました。自分のリポジトリの中から、ワークスペースの外へ書いている箇所を全部数える。

数えるだけです。直すかどうかはその後で決めます。この順番にしたのは、直しながら数えると、直せなかったものを忘れるからです。

書き込み先を洗い出す最小のスクリプト

やりたいことは単純です。リポジトリ内のスクリプトと設定ファイルを走査して、書き込み系の操作を見つけ、その行き先がリポジトリの外を指しているなら報告する。

最初に書いたのはこれくらいの短さでした。

#!/usr/bin/env python3
"""ワークスペース外への書き込みを洗い出す(第1版・リテラルのみ)"""
import os, re, sys
 
WRITE_PATTERNS = [
    (re.compile(r'(?:^|\s)>>?\s*("?)([^\s"\'|;&]+)\1'), "shell redirect"),
    (re.compile(r'\b(?:cp|mv|rsync)\b[^\n]*?\s("?)(/[^\s"\']+|\.\.[^\s"\']*)\1\s*$'), "copy dest"),
    (re.compile(r'open\(\s*["\']([^"\']+)["\']\s*,\s*["\'][^"\']*[wax][^"\']*["\']'), "python open(w)"),
    (re.compile(r'--out(?:put)?[= ]\s*("?)([^\s"\']+)\1'), "cli --out"),
]
SKIP_DIRS = {".git", "node_modules", ".next", "__pycache__"}
TEXT_EXT = {".sh", ".bash", ".py", ".mjs", ".js", ".ts", ".json", ".yml", ".yaml", ".toml"}
 
def audit(root):
    root = os.path.abspath(root)
    for dirpath, dirnames, filenames in os.walk(root):
        dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
        for name in filenames:
            if os.path.splitext(name)[1] not in TEXT_EXT:
                continue
            path = os.path.join(dirpath, name)
            lines = open(path, encoding="utf-8", errors="ignore").read().splitlines()
            for lineno, line in enumerate(lines, 1):
                for pattern, kind in WRITE_PATTERNS:
                    m = pattern.search(line)
                    if not m:
                        continue
                    raw = m.group(m.lastindex).strip('"\'')
                    if raw.startswith(("$", "~", "%")):
                        print(f"UNRESOLVED\t{os.path.relpath(path, root)}:{lineno}\t{kind}\t{raw}")
                        continue
                    target = raw if os.path.isabs(raw) else os.path.normpath(os.path.join(dirpath, raw))
                    if not (target + os.sep).startswith(root + os.sep):
                        print(f"OUTSIDE\t{os.path.relpath(path, root)}:{lineno}\t{kind}\t{target}")
 
if __name__ == "__main__":
    audit(sys.argv[1] if len(sys.argv) > 1 else ".")

TEXT_EXT で拡張子を絞っているのは、画像やバイナリを開いて時間を使わないためです。SKIP_DIRSnode_modules を入れておかないと、依存パッケージのビルドスクリプトが大量に引っかかります。

検証用に、私の構成を小さくなぞった雛形を用意しました。素材の入力が外、派生の出力が相対パス、配信ステージングが外、ログが外、キャッシュがユーザー領域という配置です。

# scripts/build_assets.sh
SRC="/Volumes/Shared/wallpaper-source/20260820"
OUT="../dist/derived"
cp "$SRC"/*.png ./work/
python3 scripts/derive.py --out "$OUT"
rsync -a ./work/ /Users/shared/cdn-staging/
echo "done" >> /var/log/wallpaper/build.log
# scripts/derive.py
import json
CACHE = "/Users/shared/agent-cache/derive.json"
def save(data):
    with open(CACHE, "w") as f:
        json.dump(data, f)
    with open("./local_report.txt", "w") as f:
        f.write("ok")

この2ファイルには、ワークスペースの外へ書く箇所が3つあります。rsync の配信ステージング、>> のログ、open(CACHE, "w") のキャッシュです。

第1版を走らせた結果がこうでした。

UNRESOLVED  scripts/build_assets.sh:5  cli --out       $OUT
OUTSIDE     scripts/build_assets.sh:6  rsync dest      /Users/shared/cdn-staging
OUTSIDE     scripts/build_assets.sh:7  shell redirect  /var/log/wallpaper/build.log

summary: outside=2 unresolved=1

3つのうち2つです。open(CACHE, "w") が丸ごと消えています。

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
自分のプロジェクトのどこがワークスペースの外へ書いているかを、更新前に機械で洗い出せるようになります
読み取り専用になって初めて自動処理が止まる、という後追いの事故を避けられるようになります
洗い出した行き先をワークスペースに含めるか、認可を通すか、出力先そのものを移すかを、自分の構成に合わせて選べるようになります
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

Antigravity 基本2026-08-17
Antigravity CLI の会話データベースは、重複を消しても 1 バイトも縮みません
会話データベースの肥大化を、スキーマを知らないまま棚卸しする手順です。削除しただけでは縮まない理由と、freelist_count を回収可能量と読み違えたときに起きることを実測で切り分けます。
Antigravity 基本2026-08-10
何にも一致しないつもりの1行が、全コマンドを承認していました — allowlist の空集合を棚卸しする
コマンド語がゼロ個に分解される allowlist entry が、すべてのコマンドに一致して自動承認していた問題。手元の設定を分解して測り、修正で閉じた穴と閉じなかった穴を切り分け、先頭トークン列一致へ書き換えるまでの手順をまとめました。
Antigravity 基本2026-07-27
&& の右側は誰が承認していたのか — コマンドを分解してから allow ルールに当てる
承認画面に出ていたのは、実行されるコマンドの一部でした。&& や ; で連結したコマンドを引用符とコマンド置換を越えずに分解し、セグメント単位で allow ルールに当てるハーネスを、自分の運用コマンド45本での実測とともにまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →