四つの Next.js サイトのソースと、素材と、書きかけのドキュメントを一つのフォルダにまとめて、そこを Cowork に接続しております。ある朝、無人で走らせていた点検タスクのログに、途中で切れた検索結果だけが残っておりました。
エラーは出ておりません。件数が少ないまま、タスクは次の工程へ進んでおりました。無音で間違えるというのは、いちばん気づきにくい壊れ方です。
手元で同じ検索を叩き直してみました。grep -rl は40秒たっても返ってきません。ripgrep に替えると7秒で返ります。ところが件数は、速いほうが多いのです。速くて多い——どちらかが嘘をついているはずだと思いながら、しばらく画面を眺めておりました。
最初にお伝えしたいのは、嘘をついていたのは検索ではなく、そのフォルダを歩き切れると考えていた私の前提だった、ということです。
同じ語を同じフォルダで探して、件数が三通りに割れました
接続しているフォルダのルートで、同じ語(premium)を四通りの方法で探しました。40秒を上限として、そこで返らないものは打ち切りとしています。個人開発で使っている実際の作業フォルダですので、数字はそのまま私の環境の値です。
| コマンド | 件数 | 所要 | 完走 |
grep -rl premium . | 314(打ち切り時点) | 40秒超 | × |
rg -l premium . | 4,142 | 7.6秒 | ○ |
rg -l --no-ignore premium . | 3,443(打ち切り時点) | 40秒超 | × |
rg -l --no-ignore --hidden + 除外指定 | 4,278 | 13.3秒 | ○ |
完走したのは二つだけです。そして完走した二つのあいだにも、136件の差がありました。
無人タスクのログに残っていた「314件」は、四行目の 4,278件 に対する数字だったわけです。割合にして 7.3% です。監査としては、ほとんど何も見ていないのと変わりません。
歩く対象の八割は、自分が書いたものではありませんでした
なぜ grep -rl が終わらないのかを、歩く対象の数から確かめました。フォルダ全体を1パスで分類した結果です。
| 区分 | ファイル数 | 割合 |
| 合計 | 103,845 | 100% |
node_modules の中 | 81,359 | 78.3% |
.git の中 | 7,092 | 6.8% |
__pycache__ の中 | 12 | 0.0% |
| 残り(実際の作業対象) | 15,382 | 14.8% |
自分が書いたもの、あるいは自分が読みたいものは、全体の 14.8% しかありませんでした。残りは依存パッケージと Git の内部データです。
走査時間にもそのまま出ます。find . -type f -name "*.json" は 3,867件 を返すのに 15.03秒 かかりました。node_modules・.git・.next を -prune で外すと、56件 を 2.41秒 で返します。件数で69倍、時間で6倍の差です。
ここで一つ、思い込みが外れました。四つのサイトを同じ構成で置いているつもりでおりましたが、node_modules があるのは三つだけで、一つには存在しませんでした。同じ棚に並べたつもりの箱が、中身の重さまで同じとは限りません。 走査の重さはリポジトリの数ではなく、そのとき展開されている依存の有無で決まります。
既定の検索が黙って外していたのは、いちばん見たい場所でした
差の136件が何なのかを、置き場所ごとに並べました。
| 置き場所 | 件数 | 中身 |
rorklab.net/public | 80 | ビルドで生成される HTML(Git 管理外) |
gemilab.net/public | 48 | 同上 |
antigravitylab.net 配下 | 3 | 運用スクリプトと、削除途中の一時ファイル |
.agents/skills | 3 | エージェントに読ませているスキル定義 |
.auto-memory | 2 | エージェント自身の記憶ファイル(索引を含む) |
ripgrep は既定で .gitignore を尊重し、先頭がドットのファイルを飛ばします。速さの正体はこれです。安全側に倒した親切な既定であり、ふだんのコード検索ではありがたい振る舞いです。
ただ、エージェントに渡したフォルダを点検する場面では、話が逆になります。飛ばされた筆頭が、エージェント自身の記憶と、エージェントに読ませているスキル定義だったのです。いちばん見ておきたい場所が、いちばん確実に視界から外れておりました。
速い検索は、速いのではありません。見ていないのです。
この一文に辿り着くまで、私は「件数が多いほうが網羅的だろう」と考えておりました。実際には、完走した二つのうち件数が少ないほうが、より多くを飛ばしていただけでした。
削除途中の一時ファイル(.fuse_hidden で始まるもの)が6件混ざっていたのも、この方法で初めて見えました。エディタが掴んだまま消されたファイルが、ディスク上に残り続けていたものです。どちらも、既定の検索では一度も目に入らない種類のものでした。
渡す前に一度だけ、走査の範囲を測る
毎回この確認をするわけにはまいりません。フォルダをつなぎ直したときに一度だけ走らせて、範囲の地図を作っておく道具を用意しました。実際に私が使っているものです。
#!/usr/bin/env bash
# scope-probe.sh — 接続フォルダをエージェントに渡す前に、走査の範囲を一度だけ測る。
# 使い方: ./scope-probe.sh <フォルダ> [検索語]
set -uo pipefail
ROOT="${1:?usage: scope-probe.sh <folder> [pattern]}"
PAT="${2:-TODO}"
SKIP=(node_modules .git .next .venv dist build vendor __pycache__)
cd "$ROOT" || exit 1
# --- 1. 何本のファイルを歩くことになるのか(find は1パスだけ流す) ---
find . -type f -print0 2>/dev/null | awk -v RS='\0' -v skip="${SKIP[*]}" '
BEGIN { n = split(skip, s, " ") }
{ total++
hit = 0
for (i = 1; i <= n; i++) if (index($0, "/" s[i] "/")) { c[s[i]]++; hit = 1; break }
if (!hit) work++ }
END {
printf " 合計 %8d\n", total
for (i = 1; i <= n; i++) if (c[s[i]]) printf " %-10s %8d (%4.1f%%)\n", s[i], c[s[i]], c[s[i]]*100/total
printf " 実作業 %8d (%4.1f%%)\n", work, work*100/total }'
# --- 2. 既定の検索と、範囲を明示した検索で件数が変わるか ---
GLOBS=(); for d in "${SKIP[@]}"; do GLOBS+=(-g "!**/$d/**"); done
t0=$(date +%s%3N); rg -l --no-ignore --hidden "${GLOBS[@]}" -- "$PAT" . 2>/dev/null | sort > /tmp/scope_full.txt; t1=$(date +%s%3N)
t2=$(date +%s%3N); rg -l -- "$PAT" . 2>/dev/null | sort > /tmp/scope_default.txt; t3=$(date +%s%3N)
echo " 範囲を明示 $(wc -l < /tmp/scope_full.txt) 件 $(( t1 - t0 )) ms"
echo " 既定のまま $(wc -l < /tmp/scope_default.txt) 件 $(( t3 - t2 )) ms"
# --- 3. 既定が黙って飛ばした分を、置き場所ごとに見せる ---
MISS=$(comm -23 /tmp/scope_full.txt /tmp/scope_default.txt | tee /tmp/scope_missed.txt | wc -l)
echo " 既定の取りこぼし ${MISS} 件"
[ "$MISS" -gt 0 ] && awk -F/ '{print " " $2 "/" $3}' /tmp/scope_missed.txt \
| sort | uniq -c | sort -rn | head -5
# 取りこぼしゼロが正常。1件でもあれば、その置き場所を走査設計に書き足す。
[ "$MISS" -eq 0 ]
実行すると、こう返ります。
$ ./scope-probe.sh "/path/to/workspace" premium
合計 103845
node_modules 81359 (78.3%)
.git 7092 ( 6.8%)
__pycache__ 12 ( 0.0%)
実作業 15382 (14.8%)
範囲を明示 4277 件 11132 ms
既定のまま 4142 件 6370 ms
既定の取りこぼし 136 件
78 rorklab.net/public
46 gemilab.net/public
3 .agents/skills
2 rorklab.net/src
2 gemilab.net/src
find を1パスに抑えて awk で分類しているのは、走査そのものが重い作業だからです。区分ごとに find を呼び直すと、確認のための確認で数十秒を溶かします。
最後の行で終了コードを立てているのは、この道具を無人の点検にも組み込めるようにするためです。取りこぼしがゼロなら 0、1件でもあれば 1 を返します。CI や PostToolUse から呼べば、フォルダの構成が変わった日に気づけます。
除外リストは、足すほど安全にはなりません
このスクリプトを書いてから、思ってもみなかった向きの取りこぼしが出ました。範囲を明示したほうから落ちて、既定のほうにだけ残っていたファイルが1件あったのです。
./_documents/_quality_audit/_scripts/__pycache__/article_gate.py.bak_2026-09-02
__pycache__ を除外リストに足したせいです。そこに、実在するスクリプトの日付き控えが紛れ込んでおりました。Python の生成物が入る場所だから見なくてよい、という判断が、人が置いた本物の控えを一緒に視界の外へ押し出していたわけです。
除外を足すのは、速さと引き換えに盲点を作る行為です。ひとつ足すたびに「そこに人が置いたものが混ざっていないか」を一度だけ確かめる——この手間を省いたところが、そのまま穴になります。
私は最初のうち、除外リストを長くすればするほど走査が賢くなると考えておりました。結果は芳しくありませんでした。いまは、除外を足したらその場で comm の差分を見る、という順番にしております。
走査の既定を、コマンドの側に宣言する
.gitignore や .rgignore に書けば済むように思えますが、ここには落とし穴があります。--no-ignore を付けた時点で、.gitignore・.ignore・.rgignore はまとめて無効になるからです。手元で確かめました。
# sub/ を .rgignore に書いた状態
$ rg -l needle .
./top.txt # sub/ は飛ばされる
$ rg -l --no-ignore needle .
./sub/a.txt # .rgignore ごと無効になるので見える
./top.txt
つまり「無視ファイルに範囲を書く」やり方と「--no-ignore で全部見る」やり方は併用できません。片方を選ぶしかないのです。
私は後者に寄せております。無人のタスクでは、リポジトリごとに違う .gitignore の中身に結果が左右されるより、コマンドの側に範囲を書いたほうが、結果を読むときに悩まずに済むからです。
# 走査の既定をひとつに固定する。無人タスクからも手元からも同じものを呼ぶ。
scan() { # scan <検索語> [パス]
rg -l --no-ignore --hidden \
-g '!**/node_modules/**' -g '!**/.git/**' -g '!**/.next/**' \
-- "$1" "${2:-.}"
}
--hidden を必ず添えるのが要点です。これが無いと、先頭がドットのディレクトリ——エージェントの設定と記憶が入っているところ——が、--no-ignore を付けていても外れたままになります。
残る三つの落とし穴
ひとつめ。クラウド同期のフォルダでは、まだ実体化していないファイルが bash 側の検索から静かに漏れます。範囲を正しく宣言しても、ここだけは別の対処が要ります。仕組みはCowork の bash が『ファイルがありません』と言うのに Finder には見える理由に書き残しました。
ふたつめ。件数が多い検索が正しいとは限りません。今回で言えば、--no-ignore だけを付けた三行目は、件数も時間も最悪でした。無視設定を外しただけで範囲を宣言しないと、node_modules を全部読みに行って時間切れになります。
みっつめ。走査の予算も設計のうちです。無人タスクの一回の実行に使える時間は有限で、重い検索を二つ並べればそれだけで上限に届きます。私は点検系のタスクでは、範囲を明示した検索を一回だけ、と決めております。同じ理由で、フォルダを二つのタスクが同時に触る構成も避けるようにしました。この判断に至った経緯は連携フォルダに置いたロックが、二度目の無人実行を締め出すまでに書いております。
渡す範囲そのものを絞るという手もあります。何を渡さないかを決める話は、鍵の扱いから入るのがいちばん早いかもしれません。フォルダを AI に渡す前に鍵を数えたら、ファイル名で見つかるのは18件中5件でしたに、その数え方をまとめております。
次の一歩
いまつないでいるフォルダで、scope-probe.sh を一度だけ走らせてみていただければと思います。見るのは最後の一行、既定の取りこぼしの件数だけで十分です。
ゼロなら、そのフォルダについては既定のままで構いません。一件でも出たなら、そこが無人タスクの見ていない場所です。私の場合は136件で、その中にエージェント自身の記憶が入っておりました。
つないだ日に一度だけ測っておけば、無音で間違える朝がひとつ減ります。お読みいただきありがとうございました。