夜間の自動処理が朝には赤くなっていました。個人開発でアプリの更新を回していると、失敗の一報を受け取るのはたいてい寝起きの数分です。
CI のログは 4,000 行あまり。原因の見当がつかないまま、私はそれを全選択してチャット入力欄に貼り付けました。入力欄が縦に伸びていく間、これはたぶん間違ったやり方だという感覚だけが先にありました。
返ってきた答えは、ログの後半に出ていた明白なスタックトレースではなく、前半の警告行についての一般論でした。エージェントが手を抜いたわけではありません。4,000 行という塊の中で、どこに重心を置くべきかを示さないまま渡した私の側の設計不足です。
v2.3.0(2026-07-13)でプレーンテキストファイルの添付とレンダリングに対応しました。これで、長いテキストをエージェントへ渡す手段が実質三つに増えています。手段が増えることは、そのまま迷いが増えることでもあります。
三つを同じログで比べ、どの条件でどれを選ぶか。切り出しと計測のスクリプトごと、以下に整理しております。
渡す手段は三つある — それぞれが壊れる場所が違う
まず整理します。Antigravity で長いテキストをエージェントに渡す方法は、現時点で次の三つです。
| 手段 |
実体 |
向く長さ |
壊れる場所 |
| チャットへ貼り付け |
会話本文の一部として扱われる |
〜120 行程度 |
長いと会話全体を圧迫し、以降のターンの参照精度が落ちる |
| .txt 添付(v2.3.0〜) |
添付として渡り、会話内でレンダリングされる |
120 行〜数千行 |
「全部読んだつもり」の要約が返る。重心の指示が要る |
| @ でのファイル参照 |
ワークスペース内のファイルを指し示す |
制限は緩い |
ワークスペース外のファイルは指せない |
三つの違いは「長さの上限」ではありません。壊れ方が違うという点が本質です。
貼り付けは会話そのものを汚します。一度貼った 4,000 行は、その後のターンでもコンテキストの一部として残り続けます。私が最初に踏んだのはこれでした。
.txt 添付は会話本文を汚さない代わりに、読み方の指定がないと重心が定まりません。長い PDF を添付したときに起きる読み飛ばしと、構造としては同じ現象です(この論点は添付した長い PDF をエージェントが読み飛ばす問題で詳しく扱っております)。
@ 参照はワークスペースに置いたファイルにしか使えません。/var/log 配下や、別マシンから持ってきたログには届きません。
つまり、選択肢は三つあるのに、条件を満たすものは場面ごとに一つか二つしかない。ここを毎回勘で決めていたのが、私の朝の失敗でした。
渡す前に削る — 4,000 行から関連区間を切り出す
三択の話をする前に、先にやるべきことがあります。削ることです。
4,000 行のログのうち、失敗の説明に必要な行はたいてい 100 行前後です。残りは正常時と変わらない進行ログで、渡しても判断の助けにはなりません。
私が使っている抽出スクリプトです。エラー行を見つけ、その前後を文脈ごと切り出します。
#!/usr/bin/env bash
# extract-log-window.sh — エラー周辺だけを切り出す
# 使い方: ./extract-log-window.sh run.log 40 10 > slice.txt
set -euo pipefail
LOG="${1:?usage: extract-log-window.sh <logfile> [before] [after]}"
BEFORE="${2:-40}"
AFTER="${3:-10}"
# 判定に使うマーカー。プロジェクトに合わせて足す
PATTERN='ERROR|FATAL|Traceback|Exception|panic:|✗|FAIL|exit code [1-9]'
if ! grep -nEq "$PATTERN" "$LOG"; then
echo "# マーカーに一致する行がありません。末尾 ${AFTER} 行を出します" >&2
tail -n "$AFTER" "$LOG"
exit 0
fi
TOTAL=$(wc -l < "$LOG")
echo "# source: $LOG (${TOTAL} lines)"
echo "# window: -${BEFORE}/+${AFTER} around /${PATTERN}/"
echo "# ---"
# 前後の窓を取り、重なった区間は --- で区切って連結される
grep -nE -B "$BEFORE" -A "$AFTER" "$PATTERN" "$LOG"
実行するとこうなります。
$ wc -l run.log
4127 run.log
$ ./extract-log-window.sh run.log 40 10 > slice.txt
$ wc -l slice.txt
143 slice.txt
4,127 行が 143 行になりました。96.5% を落とした計算です。この 143 行なら、貼り付けでも添付でも成立します。
なぜ前を 40 行、後ろを 10 行にするのか。 エラーの原因は、たいていエラー行より前に書かれているからです。後ろにあるのは後始末とプロセスの終了コードで、行数は少なくて足ります。この非対称が、私自身が何度か調整して落ち着いた比率です。
ここに落とし穴が一つあります。set -euo pipefail を書いていないシェルスクリプトを回していると、本当の失敗は最初のエラーで、以降は連鎖した二次被害であることが多い。窓を広げるほど二次被害ばかりが増えます。この場合は最初の一致だけを見て回避します。
# 最初のエラーだけを窓で切る
FIRST=$(grep -nEm1 "$PATTERN" "$LOG" | cut -d: -f1)
sed -n "$((FIRST > 40 ? FIRST - 40 : 1)),$((FIRST + 10))p" "$LOG"
手段を勘で選ばない — 行数・バイト数・トークン概算を先に出す
削ったあとで、三択のどれを使うかを決めます。ここを機械的にするために、渡す直前にサイズを測ります。
// measure-handoff.mjs — 渡す前にサイズを測り、手段を提案する
// 使い方: node measure-handoff.mjs slice.txt
import { readFileSync, statSync } from 'node:fs';
const path = process.argv[2];
if (!path) {
console.error('usage: node measure-handoff.mjs <file>');
process.exit(1);
}
const text = readFileSync(path, 'utf8');
const bytes = statSync(path).size;
const lines = text.split('\n').length;
// 概算。ログは記号と数字が多く、英文散文より1トークンあたりの文字数が短くなる
// 日本語混じりのログでは 2.0 前後に寄るため、粗い上限として 2.6 を使う
const CHARS_PER_TOKEN = 2.6;
const approxTokens = Math.ceil(text.length / CHARS_PER_TOKEN);
// 同一行の繰り返し率。高いなら削る余地がまだ残っている
const uniq = new Set(text.split('\n').map((l) => l.replace(/\d+/g, '#'))).size;
const repeatRate = 1 - uniq / lines;
const pick = () => {
if (lines <= 120 && approxTokens <= 1500) return 'paste';
if (repeatRate > 0.5) return 'shrink-first';
return 'attach';
};
console.log(JSON.stringify({
path,
lines,
bytes,
approxTokens,
repeatRate: Number(repeatRate.toFixed(2)),
recommend: pick(),
}, null, 2));
先ほどの slice.txt に対して実行した結果です。
$ node measure-handoff.mjs slice.txt
{
"path": "slice.txt",
"lines": 143,
"bytes": 11840,
"approxTokens": 4553,
"repeatRate": 0.31,
"recommend": "attach"
}
143 行でも、トークン概算では 4,500 を超えていました。行数の感覚とトークンの実態はずれます。 ログは1行が長いからです。この記事で recommend の閾値を行数とトークンの両方で持っているのは、そのずれを踏まなかった日が私にはないからです。
repeatRate を入れているのは、繰り返しの多いログをそのまま渡してしまう事故への対処です。数字部分を # に正規化した上で重複率を測り、半分以上が実質同じ行なら、渡す前にもう一度削れという判断を返します。リトライループのログは、これで高い確率で引っかかります。
三つの手段を同じログで比べる
同じ 143 行の slice.txt を、三つの手段で同じ質問とともに渡しました。質問は「この失敗の直接原因を1つ挙げ、根拠になった行を引用してください」で統一しています。
| 観点 |
貼り付け |
.txt 添付 |
@ 参照 |
| 直接原因を当てたか |
当たる |
当たる |
当たる |
| 引用の正確さ |
行が丸ごと返る |
行が丸ごと返る |
行番号付きで返ることがある |
| 次のターンへの影響 |
会話に残り続ける |
本文は汚れない |
本文は汚れない |
| あとで見返せるか |
会話を遡る必要がある |
添付として一覧に残る |
ファイルが正 |
| ワークスペース外 |
使える |
使える |
使えない |
143 行まで削ってしまえば、正答率では三つに差がつきませんでした。 差が出たのは、その後です。
貼り付けた場合、続けて「では修正案を出してください」と聞くと、会話にはまだ 143 行が居座っています。5 ターン、6 ターンと続けるうちに、この重さが効いてきます。
添付にしておくと、会話本文は質問文だけで済みます。あとから「さっきのログのこの部分だけど」と戻るときも、添付の一覧から辿れます。v2.3.0 で会話内にレンダリングされるようになったことの実利は、この「戻れること」にありました。
@ 参照は、ログをワークスペースに書き出す運用と噛み合います。私は自動処理の出力を .logs/ 配下に残しているので、そのまま指せます。ファイルが更新されればエージェントが見るものも更新される。これは添付にはない性質です。
なお、この比較は「削ったあと」の話です。削る前の 4,127 行で同じことをやると、どの手段でも精度は落ちます。 手段の選択は、削る作業の代わりにはなりません。
秘密を落としてから渡す
ログには、渡したくないものが混ざります。
CI のログには環境変数が展開された行が残ることがあり、API キーやトークンがそのまま出ていることがあります。添付は貼り付けより手軽な分、中身を目視しないまま渡してしまいやすい。この手軽さは、そのままリスクです。
抽出の段に、最小のリダクションを挟んでいます。
# extract-log-window.sh の末尾、出力の直前に挟む
redact() {
sed -E \
-e 's/(sk-[A-Za-z0-9_-]{8})[A-Za-z0-9_-]+/\1***REDACTED***/g' \
-e 's/(ghp_[A-Za-z0-9]{6})[A-Za-z0-9]+/\1***REDACTED***/g' \
-e 's/(AIza[A-Za-z0-9_-]{6})[A-Za-z0-9_-]+/\1***REDACTED***/g' \
-e 's/(Bearer )[A-Za-z0-9._-]+/\1***REDACTED***/g' \
-e 's/([A-Z_]*(TOKEN|SECRET|PASSWORD|API_KEY)[A-Z_]*=)[^[:space:]]+/\1***REDACTED***/g'
}
grep -nE -B "$BEFORE" -A "$AFTER" "$PATTERN" "$LOG" | redact
先頭数文字を残しているのは、あとで「どのキーの話か」を人間が判別できるようにするためです。全部を伏せると、キーの取り違えに気づけません。
これは網羅ではありません。プロジェクト固有の秘密は、この正規表現には引っかかりません。保存するログそのものからの除去は別の層の仕事で、保存するエージェントログから秘密を消す設計で扱った内容と役割が分かれます。ここでやっているのは、渡す直前の最後の網です。
私の落としどころ
三つの手段を、次の順で機械的に決めています。
- まず削る。
extract-log-window.sh を通す。ここを飛ばさない
- 測る。
measure-handoff.mjs を通し、recommend を見る
shrink-first が返ったら、窓を狭めるか最初の一致だけに絞ってやり直す
paste(120 行以下かつ 1,500 トークン以下)なら貼り付ける。一往復で終わる話にファイルを作らない
attach で、そのログがワークスペース内にあるなら @ 参照 をお勧めします。外にあるなら .txt 添付
- 添付・参照のときは、質問文に重心を書く。「末尾のスタックトレースを起点に」の一行があるかないかで、返答の質が変わる
6 が抜けやすい。手段を選んだ時点で仕事を終えた気になるのですが、どの手段でも「どこから読むか」の指定は要ります。 添付したのだから全部読んでいるはずだ、という期待は、私の側の甘えでした。
@ 参照の精度をさらに詰める話は、@ リファレンスで AI が触る範囲を絞るにまとめております。
次にやること
まず、手元で一番長いログに extract-log-window.sh を通してみてください。行数がどれだけ落ちるかを見るだけで、これまで何を渡していたかが分かります。
私の場合は 4,127 行が 143 行になりました。残りの 3,984 行は、渡す必要のなかったものでした。
手段が三つに増えたことは、渡し方を考え直す機会でもあります。増えた選択肢を勘で選ぶと、迷いが増えるだけで終わってしまう。測って決める形にしておけば、選択肢が増えたことがそのまま利になります。
手元のログで一度測ってみていただけたら、この記事を書いた甲斐があります。最後までお読みくださり、ありがとうございました。