土曜の朝、複数サイトの自動投稿ログを見返していて、ふと speed: "fast" をベタ書きしたまま何ヶ月も回し続けているスケジュールジョブがあることに気づきました。普段は誰も触らないコードです。だからこそ、パラメータの仕様が変わった瞬間に、誰にも気づかれずに落ちます。
2026-07-24 に claude-opus-4-7 の fast mode が廃止されます。それ以降、claude-opus-4-7 に対して speed: "fast" を指定したリクエストはエラーになります。モデル ID はそのまま生きているので、モデル引退のチェックリストには引っかかりません。にもかかわらず、特定のパラメータ組み合わせだけが期限切れになる。無人で回している処理ほど、この「モデルは残るのに設定だけ死ぬ」種類の変更に弱いのです。
ここでは、その 7/24 を静かに越えるための手順を、実際に動くコードとともに組み立てます。使用箇所の洗い出し、移行先の判断、実行時の防御、そして移行後の実測までを一続きで扱います。
7/24 に何が起きるか — speed: "fast" が例外になる
まず、変更の輪郭を正確に押さえます。廃止されるのは「モデル」ではなく「claude-opus-4-7 の fast mode という速度モード」です。Anthropic の告知では、7/24 以降 claude-opus-4-7 に speed: "fast" を渡すとエラーになり、fast mode を使いたい場合は Opus 4.8 の fast mode へ移す必要がある、とされています。
影響範囲を整理すると次のようになります。
呼び出しパターン 7/24 以降の挙動
model=claude-opus-4-7 + speed=fastエラー(要移行)
model=claude-opus-4-7(speed 指定なし)通常どおり動作
model=claude-opus-4-8 + speed=fastfast mode で動作
厄介なのは、多くのコードで speed は「一度書いて忘れる」パラメータだという点です。レイテンシを詰めたい工程でだけ付け、その後は放置される。対話的に使っているアプリなら例外が出た瞬間に気づけますが、深夜のバッチや週次のスケジュールジョブでは、失敗ログが誰にも読まれないまま積み上がります。私自身、無人ジョブが静かに止まる事故を何度か経験していて、そのたびに「気づくのが3日遅れた」ことのコストの大きさを痛感しています。
だからこの移行の目的は、単に claude-opus-4-7 を claude-opus-4-8 に置換することではありません。「どこで fast mode を使っているか」を人手の記憶に頼らず把握し、移行漏れがあっても実行時に安全側へ倒れる状態を作ること です。
具体的には、次の3段階で順に進めます。
コードベースから fast mode の使用箇所を機械的に検出する
箇所ごとに移行先(Opus 4.8 の fast mode か、speed の除去か)を決める
移行漏れに備え、実行時に fail-closed で守るガードを一枚挟む
まず現状を洗い出す — fast mode 使用箇所を機械的に検出
記憶や grep の一発勝負に頼ると、必ず取りこぼします。speed が変数経由で渡っていたり、共通ラッパー関数の既定値に隠れていたりするからです。そこで、単純な文字列検索ではなく「Opus 4.7 と fast が同じ呼び出しに同居している箇所」を炙り出す検査を用意します。
まずは軽量な一次スクリーニングとして、リポジトリ全体を走査します。
# 一次スクリーニング: opus-4-7 と speed/fast が近接する行を拾う
# 変数越しの指定は拾えないので、あくまで「当たりを付ける」用途
grep -rniE "opus-4-7|speed.{0,20}fast|fast.{0,20}speed" \
--include= "*.py" --include= "*.ts" --include= "*.js" \
--include= "*.json" --include= "*.yaml" --include= "*.yml" . \
| grep -viE "node_modules|/dist/|/build/"
一次スクリーニングで当たりを付けたら、次に呼び出し単位で正確に判定します。ここでは Python の呼び出しを対象に、AST を使って「同じ関数呼び出しの中に model=...opus-4-7 と speed="fast" が両方ある」ケースだけを抽出します。文字列の近接ではなく構文木で見るので、誤検出が減ります。
# fast_mode_scan.py — Opus 4.7 + speed=fast の共起を AST で検出
import ast
import pathlib
import sys
def literal (node):
"""定数文字列ならその値、そうでなければ None を返す。"""
if isinstance (node, ast.Constant) and isinstance (node.value, str ):
return node.value
return None
def scan_file (path: pathlib.Path):
"""1ファイルを走査し、危険な呼び出しの行番号を返す。"""
hits = []
try :
tree = ast.parse(path.read_text( encoding = "utf-8" ))
except ( SyntaxError , UnicodeDecodeError ):
return hits # 解析不能なファイルは一次スクリーニングに委ねる
for node in ast.walk(tree):
if not isinstance (node, ast.Call):
continue
model_val, speed_val = None , None
for kw in node.keywords:
if kw.arg == "model" :
model_val = literal(kw.value)
elif kw.arg == "speed" :
speed_val = literal(kw.value)
# model が変数越しでも、speed=fast があれば要確認として拾う
risky_model = model_val is not None and "opus-4-7" in model_val
risky_speed = speed_val == "fast"
if risky_speed and (risky_model or model_val is None ):
hits.append((node.lineno, model_val or "<変数>" , speed_val))
return hits
def main (root = "." ):
total = 0
for path in pathlib.Path(root).rglob( "*.py" ):
if any (p in path.parts for p in ( "node_modules" , "dist" , "build" , ".venv" )):
continue
for lineno, model, speed in scan_file(path):
print ( f " { path } : { lineno } model= { model } speed= { speed } " )
total += 1
print ( f " \n 合計 { total } 箇所" )
# CI に組み込む場合は、検出があれば非ゼロ終了で気づけるようにする
return 1 if total else 0
if __name__ == "__main__" :
sys.exit(main(sys.argv[ 1 ] if len (sys.argv) > 1 else "." ))
実行結果はたとえば次のようになります。
services/summarizer.py:88 model=claude-opus-4-7 speed=fast
jobs/nightly_digest.py:41 model=<変数> speed=fast
合計 2 箇所
2件目のように model=<変数> で出た箇所は、変数の中身を人の目で確認します。共通ラッパーの既定値に claude-opus-4-7 が入っていれば、この1行が実は複数ジョブに波及しているかもしれません。私はこの手のスキャンを CI に組み込み、検出があればビルドを失敗させる運用にしています。棚卸しを「思い出したときにやる作業」から「毎回自動で走る検査」に変えるだけで、移行漏れの不安がかなり減ります。
移行先を決める — Opus 4.8 fast mode か、speed を落とすか
検出できたら、箇所ごとに移行先を決めます。反射的に「全部 4.8 の fast mode へ」と置換したくなりますが、いったん立ち止まる価値があります。そもそもその工程に fast mode が要るのか、を問い直す機会でもあるからです。
選択肢 向いている工程 注意点
Opus 4.8 + speed=fast に移行 対話 UI・低レイテンシが体感品質に直結する箇所 4.8 の fast mode の料金・挙動を実測で確認する
speed 指定を落として通常速度に夜間バッチ・週次集計など待ち時間が問題にならない箇所 fast を外すと出力傾向が変わる場合があるため回帰確認
工程ごとに使い分ける 同一パイプライン内で対話部と集計部が混在する場合 設定を一元管理し、ベタ書きを残さない
個人開発の私の運用では、無人で回すバッチのほとんどは「速さより静かな安定」を優先したいので、この機会に speed 指定そのものを外した箇所が多くありました。fast mode は体感速度が効く対話的な場面に絞る、という切り分けです。夜間バッチのように待ち時間が問題にならない工程では、私は speed 指定を外すことを推奨します。工程ごとの速度と労力の割り振りは、effort パラメータを工程ごとに振り分けて Claude の出力コストと待ち時間を整える の考え方とも相性がよく、あわせて棚卸しすると判断が一貫します。
実行時に fail-closed で守る — capability プローブ
移行作業をどれだけ丁寧にやっても、見落としはゼロにはなりません。だからコード側にも、期限切れの設定に当たったときに安全側へ倒れる仕組みを一枚挟みます。ここでの「安全側」とは、例外を握りつぶして無言で続行することではなく、明示的に検知し、代替設定へ落として、その事実をログに残すこと です。
# safe_speed.py — 廃止済みの speed 指定を実行時に無害化するガード
import datetime as dt
import logging
logger = logging.getLogger( "model_guard" )
# Opus 4.7 fast mode の廃止日(UTC 基準で保守的に扱う)
FAST_MODE_SUNSET = dt.date( 2026 , 7 , 24 )
def resolve_call_options (model: str , speed: str | None ,
today: dt.date | None = None ) -> dict :
"""model と speed を安全な組み合わせに正規化して返す。
- 廃止日以降に opus-4-7 + fast が来たら、fast を落として続行する
- 判断はすべてログに残し、無言のフォールバックにしない
"""
today = today or dt.date.today()
opts = { "model" : model}
wants_fast = speed == "fast"
is_legacy_fast = "opus-4-7" in model and wants_fast
if is_legacy_fast and today >= FAST_MODE_SUNSET :
# 期限切れの組み合わせ: fast を外して通常速度で続行
logger.warning(
"opus-4-7 fast mode は %s に廃止済み。speed 指定を落として続行します "
"(呼び出し元の移行が未完了です)" , FAST_MODE_SUNSET .isoformat())
return opts # speed を付けない
if speed is not None :
opts[ "speed" ] = speed
return opts
# 使い方
opts = resolve_call_options( "claude-opus-4-7" , "fast" )
# opts == {"model": "claude-opus-4-7"} # 廃止日以降は fast が外れる
response = client.messages.create( max_tokens = 1024 , messages = [ ... ], ** opts)
ポイントは、廃止日を 境界のある定数として1箇所に置く ことです。日付を各所にベタ書きすると、次に別のパラメータが期限切れになったとき同じ苦労を繰り返します。この「期限を日付で管理し、リクエスト時に先回りする」発想は、モデルは残るのにパラメータだけ期限切れになる Claude API リクエストの廃止を日付で先回りする設計 で扱っているパターンを、fast mode という具体例に当てはめたものです。
なお、ここで fast を黙って外すのは「とりあえず止めない」ための応急処置であって、正解ではありません。logger.warning が出たら移行が未完了だという合図なので、ログを監視対象に入れておき、警告が出た箇所は後で正式に移行します。
無人パイプラインでの移行シム
対話アプリなら上のガード1枚で足りますが、複数のスケジュールジョブが同じクライアントを共有している場合は、呼び出しごとに resolve_call_options を書くのは現実的ではありません。そこで、クライアントを薄くラップして「全呼び出しが自動でガードを通る」ようにします。
# guarded_client.py — messages.create を横断的に守るラッパー
class GuardedMessages :
def __init__ (self, inner):
self ._inner = inner
def create (self, * , model, speed = None , ** kwargs):
opts = resolve_call_options(model, speed) # 上のガードを再利用
return self ._inner.create( ** opts, ** kwargs)
class GuardedClient :
"""既存クライアントと同じ .messages.create インターフェースを保つ。"""
def __init__ (self, inner):
self ._inner = inner
self .messages = GuardedMessages(inner.messages)
# 既存コードは client を差し替えるだけでよい
client = GuardedClient(Anthropic())
この形にしておくと、移行が済んでいないジョブがあっても、少なくとも 7/24 以降に一斉停止する事態は避けられます。各ジョブは通常速度で動き続け、警告ログが移行すべき箇所を教えてくれます。無人運用で怖いのは、失敗そのものより「失敗に気づけないこと」です。停止を1件のエラーではなく、監視できる警告の連なりに変換しておく。この一手間が、後から効いてきます。
モデルが予告なく使えなくなる事態全般への備えとしては、モデルが予告なく使えなくなる日に備えるルーター設計 のステートマシン設計も参考になります。fast mode の廃止は、その大きな枠組みの中の一事例として扱うと、対処が場当たり的になりません。
移行後に確認すること — レイテンシとコストの実測
「4.8 の fast mode に移したから速度は同じはず」と決めつけず、移行後は必ず自分の環境で測ります。モデルもモードも変わっているので、体感も請求も変わり得ます。私は移行のたびに、代表的な数呼び出しでレイテンシと出力トークン数を記録し、移行前後を並べて見るようにしています。
# measure.py — 移行前後を同一プロンプトで比較する簡易計測
import time
def measure (client, model, speed, prompt, runs = 5 ):
latencies, out_tokens = [], []
for _ in range (runs):
start = time.perf_counter()
opts = { "model" : model}
if speed:
opts[ "speed" ] = speed
r = client.messages.create( max_tokens = 512 ,
messages = [{ "role" : "user" , "content" : prompt}],
** opts)
latencies.append(time.perf_counter() - start)
out_tokens.append(r.usage.output_tokens)
latencies.sort()
p50 = latencies[ len (latencies) // 2 ]
return { "p50_sec" : round (p50, 2 ),
"avg_out_tokens" : sum (out_tokens) // len (out_tokens)}
# 移行前後を同じプロンプトで測って並べる
prompt = "次の変更履歴を3行で要約してください: ..."
# before = measure(old_client, "claude-opus-4-7", "fast", prompt)
# after = measure(new_client, "claude-opus-4-8", "fast", prompt)
見るべきは、レイテンシの中央値(p50)と出力トークン数の傾向です。出力トークンが増えていれば、同じ料金単価でも実際の請求は上がります。速度が要件に対して十分なら、speed を外して通常速度にした方が安く済むこともあります。「fast だから速い・fast だから高い」と単価だけで判断せず、自分のプロンプトでの実測を1回挟むだけで、無駄な支払いを避けられます。
よくある落とし穴
移行作業そのものより、周辺で足をすくわれることが多いので、実際に引っかかった2点だけ挙げます。
ひとつは、共通ラッパーの既定引数に speed="fast" が潜んでいるケース です。個々の呼び出しには speed が書かれていないのに、ラッパーの def create_message(..., speed="fast") で一律に付与されている。呼び出し側だけを grep すると全く見つからず、廃止日に一斉に落ちて初めて気づきます。既定引数まで含めて確認するのが確実です。
もうひとつは、廃止日をローカルのタイムゾーンで判定してしまうこと です。日付の境界付近では、判定を UTC で保守的に行わないと、ジョブの実行地とサーバー側の切り替えタイミングがずれて、想定と1日違う挙動になることがあります。上のガードで廃止日を date 定数として扱い、境界を一箇所に閉じ込めたのはこのためです。
まとめ
いま手元のリポジトリで、この記事の fast_mode_scan.py を1回走らせてみてください。1件も出なければ安心して 7/24 を迎えられますし、出たなら、それが移行すべき箇所の正確な一覧です。まずは検出、次に fail-closed のガードを1枚、という順で進めれば、慌てずに越えられます。
無人で回している処理ほど、こうした「静かな期限切れ」に弱いものです。私自身もまだ、すべてのジョブを完全に把握できているとは言えません。それでも、記憶ではなく検査に頼る仕組みへ少しずつ移していくことで、朝いちばんにログを見て青ざめる回数を減らせています。同じように運用を続けている方の助けになれば幸いです。