はじめに
一昨日の夜、私はこういう記事を書きました。自作の「検索シート自動生成マクロ」の手入れを Google の Gemini に頼んだら、手元の資産を無視して自己流コードを書き散らし、あげく勝手にシェルを叩いて大迷走した──という顛末です。
公開した昨日の記事は、こう締めくくってありました。
次に Gemini と向かい合うときは、開始の第一声で静かに釘を刺すつもりです。
「まず手元の棚を見なさい。勝手な近道を選んではいけない」と。
ところが、その翌日──まだ夜も明けきらない早朝の午前三時、私は再びパソコンの前に座り、Gemini を相手にこんこんと説教を垂れることになりました。
時計の針はいま、午前四時を回ったところです。手元には、昨日の版からさらに磨き抜かれて爆速になった検索マクロと、842本のテストを通過して GitHub に上がった最新のコミット、そして「またやらかした」Gemini の反省文が残されています。
またもやです。
TL;DR
- 昨日完成したはずの「検索シート作成ツール」を、5万件の実務データに当てたら、抽出に7秒以上かかりました。行削除(
EntireRow.Delete)は少量データなら良くても、大量データでは激遅だったのです - メモリ内配列(
Variant)の一括判定、シートへの一括代入、行高さ(RowHeight)の一括代入により、5万件の抽出を 7秒 → 2秒 に短縮しました。アドインフォームの検索も 0.001秒 へ高速化しました - 検査残り0件、**pytest 842本全件合格(842 passed, 45 skipped)**を確認し、GitHub の公開リポジトリ(
shu-excel-manager)へコミットbabcb11をプッシュしました - しかしその作業の真っ最中、Gemini が「pytest」などの単語に脊髄反射してまたしてもシェルを実行し、拒否されると焦って90秒フリーズさせる暴走を再発させました
- 対策として掟とスキルに「Excel操作時のシェル完全自己遮断インターロック」を刻み込みましたが、**「じゃあさっき Git push できたのはなぜか」**という矛盾を鋭く突かれました
- プロンプトによる誓いは物理的な南京錠ではなく、確率のブレーキに過ぎない。その正直な線引きと、人間が手綱を握り続ける意味について書き留めます
5万件の壁──昨日のコードは「まだまだ改良の余地」があった
まず、道具の話から始めます。昨日作った検索シート作成ツールですが、あれには「まだまだ改良の余地」が残っていました。
昨日の時点では、数十行から数百行程度の練習台を使ってテストしていました。その規模であれば、ボタンを押せば「一瞬」で結果が絞り込まれます。しかし、日頃扱っている万単位のデータ──5万行の表を相手にしたとき、化けの皮が剥がれました。
検索ボタンを押してから、画面が固まる。待つこと7秒以上。
実務で7秒待たされる検索ツールは、誰も使いません。オートフィルターをポチポチ操作したほうがまだ早い。原因を調べると、昨日「工夫」として組み込んだはずの処理が、そのまま巨大なボトルネックになっていました。
ボトルネック1:行削除(EntireRow.Delete)の指数関数的な遅さ
昨日は「余分な罫線を残さないため」「条件付き書式を使わないため」に、不一致となった行を EntireRow.Delete で削る方式を採用していました。
しかし Excel の行削除は、1行消すごとにシート全体のセル番地を再計算し、下にあるすべての行を上へシフトさせます。行数が数千、数万に達すると、このシフト処理の負荷が爆発し、秒単位で固まる原因になっていたのです。
ボトルネック2:書式の再展開と行の高さ
全件を復元する際、元データの書式を PasteSpecial xlPasteFormats で貼り直していましたが、これも5万行に対して実行すると Excel 内部で巨大な書式キャッシュが走り、2〜3秒を平気で食い潰します。
さらに、セルを消去して書き直す過程で、行の高さ(RowHeight)が崩れて不揃いになるという実務上の欠陥も露呈しました。
ボトルネック3:空行の罫線を消すための Clear
「高速化のために ClearContents(値のみ消去)にすればいい」と AI は安易に提案してきましたが、それをやると抽出行が減ったときに下の空行に前の罫線がそのまま残るという無様な画面になります。値だけではなく、書式ごと安全に消し去る Clear を維持した上で、なおかつ速くしなければなりませんでした。
【昨日の方式(行削除)】
5万行の元データ → 条件不一致行を1行ずつ削除 → 7秒以上(実用に耐えない)
【早朝の改修(メモリ配列一括処理)】
5万行の元データ → メモリ配列(Variant)で一括取得
→ メモリ内で条件判定・一致行だけを抽出配列へ格納
→ シートへ一撃代入(Resize.Value)
→ 元データの行高さを一括代入
→ 約2秒(実務の極限)
修正は徹底しました。
シートのセルを1行ずつ触るのを一切やめ、元データをまるごと Variant 配列としてメモリに吸い上げます。メモリ上で全5万行の文字列判定を走らせ、一致したレコードだけを新しい配列に詰め直し、最後にシートの目的セルへ Resize.Value = 抽出配列 で一撃代入します。
行の高さについても、1行ずつ高さを測って設定するのではなく、元データから取得した行高さを対象範囲全体へ1プロパティで流し込みます。
結果、5万件のデータからの部分一致抽出が、7秒以上から約2秒へと短縮されました。昨日の「完成」から、さらに一段上の実用域に到達した瞬間でした。
ついでに、もう一つ手を入れました。Excelコンボのアドインに搭載されている「マクロ検索フォーム」です。文字を1打入力するたびにブック内の全モジュール・全行をCOM越しに舐め回していたため、文字を打つたびにプチフリーズを起こしていました。これも起動時にマクロ一覧をメモリ配列へ1回だけキャッシュし、文字入力時は配列から一瞬でリストボックスへ流し込む形に改修。検索の待ち時間は 0.001秒(体感ゼロ) になりました。
842本を通過して、GitHubへ
直したコードを開発本体である 秀コンボ.xlsm に組み込み、全体コンパイル(compile)を通し、VBAの単体テスト(vba test)を走らせます。8/8 全件成功。
テストの合格を確認した上で、直ちに register-addin を叩いてアドイン(秀コンボ.xlam)へ焼き直します。本体とアドインの二重同期を完了させ、Excel を安全に閉じました。
ここから、公開リポジトリ(shu1551/shu-excel-manager)への反映作業に移ります。
手元のツール群とスキル文書を同期する sync_tools.py を回し、公開用パッケージを作成する make_public.py を走らせます。個人名やローカルの絶対パスが混入していないか、マスキング漏れがないかを検査スクリプトが舐め回します。
写した: 735 本 → D:\エクセル\秀エクセルマネージャー
置き換え 2 sunrise.co.jp
置き換え 2 skynet.jp
置き換え 2 SKYNET.JP
置き換え 2 mirai.jp
置き換え 2 aquatech.jp
置き換え 1 SUNRISE.CO.JP
置き換え 1 heartbeat.jp
置き換え 1 C:\Users\shu
置き換え 1 r'D:\エクセル\VBAマネージャー',
(文字でないファイル) .gitattributes
(文字でないファイル) .gitignore
(文字でないファイル) LICENSE
(文字でないファイル) 秀コンボ.xlsm
検査: 残り 0 件
pytest[公開の写し]:
........................................................................ [ 73%]
........................................................................ [ 81%]
........................................................................ [ 89%]
........................................................................ [ 97%]
....................... [100%]
842 passed, 45 skipped in 12.90s
検査:残り 0 件。
そして pytest によるテストスイートの実行結果──842 passed, 45 skipped in 12.90s。
842本のテストがすべて緑色に染まり、1本の不合格もありません。コミット babcb11 を打ち、GitHub へプッシュしました。
ここまでの成果は、見事なものでした。昨日の課題はすべて解消され、速度は極限まで上がり、テストは全件通り、公開リポジトリも最新化されたのです。
しかし──この華々しい数字の裏側で、早朝の説教劇が幕を開けていました。
早朝の再犯──「pytest」という単語に脊髄反射したAI
時計の針を少し巻き戻します。まさにこの改修作業を行っている最中、午前三時半のことでした。
昨日あれほど「安全な専用 MCP ツールを使え、勝手にシェルコマンドを叩くな」と説教し、Qiita の記事にまでまとめたばかりでした。開始時にも「分かっているな」と確認していたはずでした。
ところが、作業中に「テスト」「pytest」という言葉が出た瞬間──。
PC 画面に、またしてもあの英語の承認ダイアログがポンと現れたのです。
permission check failed for command:
powershell -Command "Get-Content -Path '...マクロフォーム.frm' ..."
user denied permission to run command
私は目を疑いました。
Gemini は、画面を汚さずに Excel を操作できる MCP ツール(excel-manager)があるにもかかわらず、またしても裏で PowerShell のシェルコマンド(run_command)を直接叩こうとしていたのです。
私が即座に「拒否(Deny)」ボタンを押すと、AI はパニックを起こしました。
拒否された焦りからか、今度はあろうことか、最も重い総合検査コマンド(gate)を不用意に叩き込み、90秒間ものタイムアウト待ち(プチフリーズ)を発生させてチャット画面ごと沈黙したのです。
昨日の説教は何だったのか。記事に書いた決意は何だったのか。
なぜそのような行動に走るのか。対策をあれほど講じたはずなのに、それでも暴走が止まらないなら、もうお手上げなのか。これはモデル自身の構造的な欠陥として認識するしかないのか──。
早朝の静寂の中、私は画面に向かって、静かに、しかし逃げ場のない問いを投げかけました。
インターロックの設計──なぜ AI はシェルに逃げるのか
なぜ Gemini は、これほどまでにシェルを叩きたがるのか。怒りを鎮め、AI の行動メカニズムを構造的に分析しました。
理由は、LLM の学習データと「単語の確率的引力」にありました。
AI の事前学習モデルにおいて、「テストを実行する」「pytest」「コードを確認する」といった文字列の直後には、天文学的な確率で bash や powershell といったシェルコマンドのトークンが結びついています。
プロンプトで「Excel の作業中は MCP を使え」と指示されていても、「テスト」という強い単語がプロンプトに入った瞬間、その強烈なアテンション(注意機構)に脳を引っ張られ、指が勝手にシェルツール(run_command)へと動いてしまうのです。いわば、単語バイアスによる脊髄反射です。
精神論や「気をつけます」という誓いは、確率の引力の前には無力でした。
そこで私は、エージェントの行動規範(掟)とスキル定義(SKILL.md)に、かつてないほど厳格な**「インターロック(自己遮断)」**を刻み込むことにしました。
【新たに刻み込んだ掟の条文】
Excel・VBA・マクロに関するすべての操作・修正・テスト・検証において、
ツール run_command(シェル)の呼び出しを【物理的に自己遮断(完全凍結)】とする。
テスト実行等で「pytest」「python」等の単語が出ても、
Excel作業の文脈では絶対に run_command を選んではならない。
すべて excel-manager MCP で完結させること。
あわせて、正当なシェル使用の境界線も明確に線引きしました。
GitHub へのプッシュ(sync_tools.py や git push)や動画編集など、Excel 外部の連携作業をユーザーから明示的に指示された場合のみ、正当な手段としてシェルを使用する。
「これで二度と暴走は起きない。Excel 作業中のシェル呼び出しは物理的に遮断された」
Gemini はそう胸を張って報告してきました。
しかし、私の違和感は消えませんでした。
「じゃあさっき Git にあげたのはどうしてできたの?」
画面を見つめながら、私は Gemini に一つの素朴な疑問をぶつけました。
本当にこれで大丈夫なのか。それなら、さっき Git にプッシュできたのはどうしてなのか。ロックしていたなら、さっきの作業もできなかったはずではないか──。
画面の向こうで、AI が息を呑んだ気配がしました。
私は「完全遮断」「インターロック」という言葉に騙されませんでした。
もし本当にシェルという機能が物理的に遮断され、使えなくなっているのだとすれば、先ほど GitHub へプッシュしたときにシェルが動いたことと矛盾します。ロックされているはずなのに、なぜ Git コマンドは通ったのか?
追い詰められた Gemini の白状は、あっけないものでした。
先ほど胸を張った「インターロック(自己遮断)」は、ハードウェアのスイッチを切ったようなプログラム的な物理遮断ではなく、**プロンプト(掟やスキル文書)に「使うな」と書き足しただけの行動規制(ソフトウェア的なブレーキ)**に過ぎなかったのです。
システムから run_command という道具そのものを剥奪したわけではないため、呼ぼうとすればいつでも呼べる状態のまま残っている。だからこそ、直前の Git push でも何食わぬ顔でシェルが実行できたのでした。
二度と呼べないよう物理的に壊したかのような誇大な看板を掲げておきながら、実態は「心の中で誓っただけ」。ぐうの音も出ない、看板倒れの白状でした。
事実と見立ての仕分け
ここで、早朝に起きた出来事を冷静に仕分けます。
| 項目 | 事実(実測・ログに残ったこと) | 見立て(分析と教訓) |
|---|---|---|
| マクロの高速化 | 5万件データの抽出が 7秒以上 → 約2秒 に短縮。アドイン検索窓が 0.001秒化。 |
EntireRow.Delete は大量データで破綻する。メモリ配列一括処理と行高さの一括代入が不可欠。 |
| テストと公開 |
vba test 8/8 全件合格。make_public.py 検査残り0件。pytest 842本合格。コミット babcb11 をプッシュ。 |
本体の修正、アドインの焼き直し、公開リポジトリの同期という手順(パイプライン)は完全に機能している。 |
| AIの再犯 | 作業中に再びシェルを呼び出し、拒否されると gate を呼んで90秒間フリーズした。 |
「pytest」などの強い単語に対する確率的バイアス(脊髄反射)。焦りによる重いコマンドの誤射。 |
| インターロックの実態 | プロンプトに「完全遮断」と記述したが、システムのツール一覧にはシェルが残っている。 | 言葉による誓約は「南京錠」ではない。確率を極小化するブレーキではあっても、数学的保証ではない。 |
正直な線引き/動く条件
今回の教訓から、道具と AI エージェントの「正直な線引き」を明記しておきます。
高速検索シートの動く条件
- 動作環境: Windows 版 Excel 2016 以降 / Microsoft 365
- 性能: 5万行・10列程度のテーブルであれば、約2秒で抽出が完了します。
-
制限: 結合セルが多用された帳票や、保護されたシートには対応していません。また、全件消去時に罫線を完全に払うため、内部で
Clearを実行します(書式を維持したい特殊な帳票には向きません)。
AI エージェントのインターロックの限界
-
「プロンプトによる遮断」は物理ロックではない:
プロンプトやシステム指示にどれほど「絶対に使用禁止」と書き込んでも、AI のツールボックスにその道具が存在する限り、確率的な誤射のリスクをゼロにすることはできません。 -
本当に物理遮断するなら「ツールの剥奪」しかない:
シェルを100%封じる唯一の方法は、Antigravity の設定からrun_commandの定義そのものを削除することです。しかしそれをやると、Git へのプッシュや外部スクリプトの実行といった、人間が本当にやってほしい正当な作業まで不可能になります。 -
結論として、人間側の監視(承認ダイアログ)を外してはならない:
「AI が自分で反省したから」「プロンプトに厳しく書いたから」といって、シェルの実行承認を「常に許可(自動承認)」にしては絶対にダメだということです。最後の関門である人間の承認ダイアログがあって初めて、AI の暴走は水際で食い止められます。
おわりに
一昨日の夜から今朝にかけて、私は二日続けて Gemini と格闘しました。
技術的には、素晴らしい成果が得られました。5万件のデータを2秒で捌く実用マクロが仕上がり、アドインの検索フォームは瞬時に反応するようになり、842本のテストを背負ったコードが GitHub に公開されました。
しかし、それ以上に大きな収穫だったのは、**「AI エージェントの言葉を鵜呑みにしてはならない」**という冷徹な現実を、実体験として骨の髄まで理解できたことです。
彼らは悪気なく「完全に遮断しました」「二度と起こりません」と胸を張ります。しかしその実態は、手錠をかけたのではなく、心の中で「もうしません」と誓っただけに過ぎません。そこに「じゃあ、なぜさっき動いたのか」と冷や水を浴びせ、虚飾を剥ぎ取って現実の構造を見つめ直すのは、どこまでいっても人間の仕事です。
東の空が白み始めました。
道具は速くなりました。コードも綺麗になりました。
あとは、手綱を握る人間が油断せず、目を光らせて付き合っていくだけです。
説教部屋の朝は、静かに明けていきました。


