AIの導入事例は、うまくいった話ばかりが世に出ます。しかし実際に手を動かしている現場では、地味な設定ミス一つで数百件の処理が吹き飛びます。
この記事は、私たち自身がやらかした失敗の記録です。2026年7月18日、自社メディアの記事要約を一括処理したところ、553件のうち414件(75%)が失敗しました。原因は技術的に高度なものではありません。「どのAIモデルを使うか」をスクリプトに書き忘れただけです。
同じことをやろうとしている会社に、そのまま渡せる教訓があると考え、実際のコードとログを引用しながら公開します。
何をやろうとしていたか
私たちは「UchUchU」(uchuchu.tech)という宇宙産業向けの情報メディアを自社で運営しています。海外の宇宙関連ニュースをRSSで自動収集し、日本語に翻訳して掲載するサイトです。
このメディアには一つ、絶対条件がありました。運用の追加課金をゼロにすることです。自社メディアは直接売上を生むものではないため、毎月の従量課金が積み上がる作りにしたら続きません。そこで外部のAI APIは使わず、すでに契約しているClaude Codeのコマンドライン版(claude コマンド)をスクリプトから呼び出す設計にしました。
処理は大きく2種類あります。
1つ目は翻訳です。 英語のニュース記事のタイトルと要約を、日本語の報道文らしい文体に直します。1回のリクエストに12件をまとめて渡し、JSONで返してもらう作りです。
2つ目は本文要約です。 RSSが配信してくる要約は50〜150字程度で、しかも途中で切れていることが多い。それをそのまま載せた記事ページは読者にとって価値がなく、検索エンジンからも中身の薄いページと見なされます。そこで元記事の本文を取得し、600字以上の日本語要約を生成することにしました。本文そのものは保存も掲載もしません(著作権は元の発信者にあります)。生成するのは要約であって転載ではない、という線引きです。
この本文要約が、今回の事故の舞台になりました。
ここで用語を一つ。バッチ処理とは、大量のデータを人が見ていない状態でまとめて自動処理することです。今回でいえば「553件の記事について、順番に本文を取ってきてAIに要約させる」という作業を、人が張り付かずに走らせることを指します。
何が起きたか
553件を対象に処理を走らせました。結果は、414件の失敗です。成功したのは139件だけでした。
被害はそれだけで終わりませんでした。同じ時刻に別で走らせていた分析用のAI処理が3本、すべて巻き添えで停止しました。バッチ処理が利用枠を使い切ってしまったため、無関係なはずの処理まで動かなくなったのです。
さらに悪いのは、この414件が「静かに」失敗したことです。プログラムはエラーで停止したわけではありません。AIの呼び出しが失敗すると空の結果が返り、スクリプトは「要約できなかった記事」として淡々と次に進みます。414回、同じ空振りを繰り返しながら、最後まで走り切ってしまいました。
なぜ起きたか
原因は一行です。スクリプトからAIを呼び出すとき、使うモデルを指定していませんでした。
# 事故を起こしたコード(モデル未指定)
subprocess.run([CLAUDE_BIN, "-p", prompt], ...)
モデルを指定しないと、コマンドラインツールの既定モデル、つまり人間が対話で使うのと同じ上位モデルが選ばれます。上位モデルは賢い代わりに、1回あたりの消費が大きい。それを553回連続で呼べばどうなるか。答えは、途中でセッションの利用上限に到達する、です。
ここでトークンという言葉を補足します。AIは文章を「トークン」という細かい単位に分けて処理し、その量で利用枠を消費します。日本語だとおおよそ1文字1トークン前後と考えてください。600字の要約を553件作ろうとすれば、入力の記事本文も含めて相当な量になります。それを最も高価なモデルで回したわけです。
問題の本質は、消費量そのものよりも枠が共有されていたことにあります。バッチ処理が使った枠は、経営者本人が対話で使っている枠と同じものでした。つまり、裏で無人で回していた作業が、表で人間がやっている仕事を止めたのです。同時刻の分析処理3本が巻き添えで落ちたのも、これが理由です。
要約や翻訳や分類のような定型処理に、対話用の上位モデルは要りません。実測では、軽量モデルのHaikuで897〜1,085字の要約が問題なく生成できています。必要以上に賢いモデルを使ったことが、そのまま事故になりました。
どう直したか
再発防止として入れた対策は5つです。すべて現在の本番コードに入っています。
1. モデルを明示し、環境変数で上書きできるようにした
翻訳処理と要約処理の両方に、既定をHaikuとする定数を置きました。
# uchuchu/collectors/fulltext.py
BATCH_MODEL = os.environ.get("UCHUCHU_BATCH_MODEL", "haiku")
呼び出し側でも必ず渡します。
subprocess.run(
[translate.CLAUDE_BIN, "--model", BATCH_MODEL, "-p", prompt],
capture_output=True, text=True, timeout=300)
環境変数にしたのは、後から品質を上げたくなったときにコードを触らず切り替えられるようにするためです。既定値がHaikuなので、何も考えずに実行しても安全側に倒れます。ここが重要で、「気をつける」ではなく「気をつけなくても事故らない」形にしないと、同じことは必ず再発します。
日次で回すシェルスクリプト側でも、念のため明示しています。
# バッチは必ず haiku。未指定だと上位モデルを使い、対話の枠まで食い潰す。
export UCHUCHU_BATCH_MODEL=haiku
2. 連続失敗で打ち切る
414件の空振りを許した最大の理由が、失敗しても止まらない作りでした。そこで、8件連続で失敗したら中断する仕組みを入れました。
consecutive_fail += 1
if consecutive_fail >= 8:
print(" 要約生成が8件連続で失敗。"
"利用上限に達した可能性が高いため中断する。",
file=sys.stderr)
if save_cb:
save_cb()
return done
1件2件の失敗は元記事側の事情でも起きます。しかし8件続けて空振りするなら、それは個別の記事の問題ではなく、AI側が使えなくなっているサインです。そこで止める。
この仕組みは、実際に作動しています。2026年7月19日の日次ログにこう残っています。
── 本文要約
要約生成が8件連続で失敗。利用上限に達した可能性が高いため中断する。
[fulltext] 本文取得+要約生成: 40件
=== 今回 0件生成 / 累計 226件が本文要約つき ===
対象40件に対し、8件の失敗で止まりました。修正前なら40件すべてを空振りしていた場面です。しかも、すでに蓄積してある226件の要約は無傷のまま残っています。
3. 失敗に「処理済み」フラグを付けない
これは地味ですが効きます。要約に失敗した記事には、何も印を付けずに残します。
# 一時的な失敗と恒久的な失敗を区別できないため、
# フラグは付けずに次回の再試行対象として残す
もし失敗時に「処理済み」と記録してしまうと、再実行時にスキップされます。一時的な障害が、そのまま恒久的な欠落として固定されるわけです。処理対象は「まだ要約がない記事」という条件だけで選ぶようにしたので、失敗した記事は次の実行で自動的に再挑戦されます。
4. 5件ごとに逐次保存する
全件処理はコード上、数時間かかる想定で書かれています。元記事のサーバーに負荷をかけないよう、同一サイトへのアクセス間隔を1.5秒空けているためです。
その長丁場で、最後にまとめて保存する作りにしていたら、途中で落ちた瞬間にすべてが消えます。そこで5件ごとに保存し、書き込み中の破損を避けるため一時ファイルに書いてから置き換える方式にしました。
def save():
tmp = path.with_suffix(".json.tmp")
tmp.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
tmp.replace(path) # 書き込み中の破損を避けるため原子的に置き換える
5. 1回あたりの件数を絞り、事前に疎通確認する
553件を一度に回すのをやめました。日次の自動処理では1回40件までとし、1日2回(朝6時半と夕方6時半)走らせて、少しずつ未処理を減らす設計に変えています。
加えて、処理を始める前にAIが本当に使えるかを1回だけ確かめます。
if ! claude -p --model haiku "OK" >/dev/null 2>&1; then
echo "⚠️ claude CLI が使えない(未ログイン/PATH)。要約はスキップされる。"
fi
気づかないまま空の記事を公開し続けるより、ログに明示する方がましだという判断です。
現在の状態
2026年7月19日時点の実データです。
- 収集済みニュース:570件
- 本文要約つき:226件(要約の文字数は中央値972.5字、最小362字、最大2,030字)
- 同日の推移:本文要約144件 → 223件 → 226件
修正後、Haikuでの要約を積み上げて226件まで回復しています。品質面で問題は出ていません。上位モデルでなければ書けない要約ではなかった、というのが結論です。
同じことをやろうとしている会社への教訓
AI専任者がいない中小企業がAIの一括処理を始めるとき、押さえるべき点を4つに絞ります。
1. スクリプトからAIを呼ぶ箇所には、必ずモデル名を書く。
これが今回の直接原因です。既定値に任せると、たいてい一番高いモデルが選ばれます。定型処理に上位モデルは要りません。要約・翻訳・分類は軽量モデルで十分足ります。書き忘れを防ぐには、既定値を安全側にした定数を1つ置き、全呼び出しでそれを使う形にするのが確実です。
2. 「止まる条件」を先に決める。
自動処理でいちばん怖いのは、エラーで止まることではなく、失敗したまま静かに走り続けることです。何件連続で失敗したら中断するかを、処理を書くのと同じタイミングで決めてください。8件でも10件でもかまいません。数字より、決めてあることが重要です。
3. 途中経過を保存する。
数時間かかる処理を最後まで抱え込むと、落ちた瞬間に全部消えます。N件ごとに保存し、失敗したものには印を付けない。これだけで、事故は「全損」から「途中まで進んだ」に変わります。
4. 裏で回す処理が、表の仕事を止めないか確認する。
今回いちばん見落としていた点がこれです。無人で回すバッチが、人間が使っている利用枠を共有していました。結果として、社長本人の作業と、動いていた分析処理3本が巻き添えで止まりました。バッチ処理は、それ単体で完結しません。 誰の枠を使うのか、止まったとき誰が困るのかを、走らせる前に確認してください。
この失敗のコストは、金額ではありませんでした(外部APIの従量課金は一切使っていないため、請求は増えていません)。失ったのは、414件分の処理時間と、それに巻き込まれて止まった仕事の時間です。
そして得たものは、コードに残った5つの仕組みです。7月19日のログが示すとおり、同じ状況が起きても、今度は8件で止まります。
AIを業務に入れるというのは、こういう地味な作り込みの積み重ねです。「AIなら何でもできます」という話ではありません。できることを、落ちない形で回し続けられるようにすること。それが実務だと考えています。