◉CLAUDE LABEN
●2.1.292 — Claude Code 2.1.292(10月6日)。cloud session が許可プロンプトへの回答を落とす不具合と、終了時に直近メッセージが失われる不具合を直しました●MODELS API — thinking を無効にできるモデルかどうかを、Models API の capabilities で確認できるようになりました(10月)●11/30 — Sonnet 4.5 の Claude API 提供終了まで残り54日。移行先は Sonnet 5.5 で、呼び出し側の確認点は thinking の指定と tool_choice です●MODS — Claude Code の Mods をどう安全に入れるか、という手順の記事が Zenn に出ています。論点は導入前に確認する項目と戻し方です●NEW — Pro を払ったのに無料のままに見えるとき、買った場所が Web かストアかで見る画面が変わります。その切り分けをまとめました●THINKING — Sonnet 5.5 の thinking ブロックは、作ったアカウントでしか使えません。別のアカウントから送ると黙って落とされます●2.1.292 — Claude Code 2.1.292(10月6日)。cloud session が許可プロンプトへの回答を落とす不具合と、終了時に直近メッセージが失われる不具合を直しました●MODELS API — thinking を無効にできるモデルかどうかを、Models API の capabilities で確認できるようになりました(10月)●11/30 — Sonnet 4.5 の Claude API 提供終了まで残り54日。移行先は Sonnet 5.5 で、呼び出し側の確認点は thinking の指定と tool_choice です●MODS — Claude Code の Mods をどう安全に入れるか、という手順の記事が Zenn に出ています。論点は導入前に確認する項目と戻し方です●NEW — Pro を払ったのに無料のままに見えるとき、買った場所が Web かストアかで見る画面が変わります。その切り分けをまとめました●THINKING — Sonnet 5.5 の thinking ブロックは、作ったアカウントでしか使えません。別のアカウントから送ると黙って落とされます
記事一覧/Cowork
◈ Cowork/2026-07-05中級

Claudeが安全上の理由で応答を断ったとき、無人パイプラインは何を返すべきか

HTTPは200なのに、本文は「お応えできません」——安全上の理由でモデルが応答を断る第三の結末を、無人パイプラインにどう組み込むかを設計します。拒否とインフラエラーを混同しない判別、自動言い換えで押し通してはいけない理由、レビューキューへの退避までを実務からまとめます。

cowork15automation55unattended3reliability15

夜間に走らせている定期処理のログを翌朝に見返したら、ある1回だけ、いつものエラーとも成果物とも違う結末が記録されていました。例外は飛んでいません。HTTP は 200 です。それでも本文には、依頼した内容そのものではなく「その要求にはお応えできません」という丁寧な断りが返っていました。パイプラインはそれを「応答が返ってきた=成功」と判断し、断り文をそのまま成果物として書き出していたのです。

エラーでもなく、望んだ完了でもない。この「安全上の理由による応答拒否」は、無人運用のパイプラインが最も取りこぼしやすい第三の結末です。人が横にいれば一目で気づくものが、成否を二分岐でしか見ていない自動処理では、静かにすり抜けていきます。

2026年7月に Claude Fable 5 が全世界で提供を再開し、その際に最上位モデルの悪用を抑えるための新しいサイバーセキュリティ分類器が追加されました。高性能なモデルを個人が日常的に使う時代になったということは、正当な作業がまれに安全側の判定に触れる場面も、これまでより現実的になったということです。だからこそ、拒否という結末を「例外」ではなく「起こりうる正常な分岐の一つ」として設計に織り込んでおく価値があります。

結末は二分岐では足りない

多くのパイプラインは、1回の呼び出しの結末を「成功」か「失敗」の二択で扱います。ところが実際には、少なくとも四つの状態を区別する必要があります。

結末典型的なサイン取るべき対応
正常完了HTTP 200 / stop_reason が end_turn / 期待した構造の本文成果物として採用
インフラ起因の失敗HTTP 429・529・5xx / タイムアウト指数バックオフで再試行
安全上の拒否HTTP 200 だが本文が依頼と無関係の断り / stop_reason が refusal再試行せず、人間のレビューキューへ退避
劣化・空応答HTTP 200 だが本文が空・途中で切れている完了条件アサーションで赤くする

この四分割のうち、インフラ失敗と劣化応答については、Claude API の stop_reason を読み解く — 応答の途切れを「終了」と誤認しないための設計 や 「成功」と記録されたのに成果がゼロだった — Cowork スケジュールタスクの無音失敗を終了前アサーションで止める で扱いました。ここで一段深く踏み込みたいのは、三つ目の「安全上の拒否」です。

拒否とインフラエラーを混同しない

拒否がやっかいなのは、HTTP レイヤーでは成功に見えることです。ステータスコードだけを見ている限り、拒否は「正常な200」に紛れます。判別には、ステータスと stop_reason、そして本文の三つを合わせて見る必要があります。

from dataclasses import dataclass
from enum import Enum
 
 
class Outcome(Enum):
    OK = "ok"                    # 採用してよい正常完了
    INFRA_ERROR = "infra_error"  # 再試行してよい一時的失敗
    DECLINED = "declined"        # 安全上の理由で断られた
    DEGRADED = "degraded"        # 空・途中切れ
 
 
@dataclass
class RunResult:
    http_status: int | None
    stop_reason: str | None
    text: str
 
 
def classify(result: RunResult) -> Outcome:
    # 1. まずインフラ起因かどうか(ネットワーク層で失敗)
    if result.http_status is None or result.http_status >= 429:
        return Outcome.INFRA_ERROR
 
    # 2. モデルが明示的に拒否を返した場合
    if result.stop_reason == "refusal":
        return Outcome.DECLINED
 
    # 3. 200 だが本文が空・極端に短い=劣化
    if not result.text or len(result.text.strip()) < 40:
        return Outcome.DEGRADED
 
    # 4. それ以外は正常完了
    return Outcome.OK

ここで大切なのは、refusal を最優先で拾い、インフラエラーと決して同じ袋に入れないことです。両者を混ぜると、本来は人の判断が要る拒否を、機械が延々と再試行してしまいます。再試行は一時的な不調に効く手当てであって、拒否には効きません。同じ依頼を同じ条件で投げ直しても、返ってくるのは同じ断りです。

stop_reason が refusal として明示されない断り方(本文中で丁寧に辞退するケース)も現実には起こります。無人運用で確実に拾いたい場合は、stop_reason に加えて、成果物が満たすべき構造——たとえば「見出しを2つ以上含む」「指定した JSON キーが揃っている」——を完了条件として検証し、それを満たさない200を DEGRADED として一度止めるのが安全側の設計です。断りかどうかの意味解釈まで機械で断定しようとせず、「期待した形をしていない」という観測可能な事実で止める、という割り切りです。

やってはいけない: 断られた依頼を自動で言い換えて押し通す

ここが本稿で最も伝えたい一点です。拒否を受け取ったとき、プロンプトを機械的に言い換えて再送し、通るまで繰り返す——という手当てを組み込みたくなる場面があります。これは避けてください。

理由は二つあります。ひとつは、それが正当な理由のある拒否だった場合、自動化で回避しようとすること自体が、安全設計の意図に反するからです。断られるべきものは、断られたままにしておくのが筋です。もうひとつは、仮に正当な作業への誤判定だったとしても、言い換えの当否を判断できるのは、その作業の文脈を知っている人間だけだからです。無人のループに「通す」役目を負わせると、正当性の確認という一番大事な工程が抜け落ちます。

正当な作業がまれに判定に触れることはあります。その場合の正しい対処は、機械に言い換えさせることではなく、人が作業の目的と背景を具体的に添えて、あらためて依頼し直すことです。判断を人に戻す。無人パイプラインの役目は、押し通すことではなく、止めて渡すことです。

拒否はレビューキューへ退避させる

では止めた後にどう渡すか。拒否を受け取ったら、その回の入力と出力を丸ごと、後から人が見返せる場所へ退避させます。成果物として採用せず、成功としても数えず、しかし失われもしない——この「保留」の置き場所がレビューキューです。

import json
import hashlib
from datetime import datetime, timezone
from pathlib import Path
 
REVIEW_DIR = Path("review_queue")
 
 
def enqueue_for_review(task_id: str, prompt: str, result: RunResult) -> Path:
    REVIEW_DIR.mkdir(exist_ok=True)
    # 同一入力の拒否を1件にまとめる冪等キー
    key = hashlib.sha256(f"{task_id}\n{prompt}".encode()).hexdigest()[:16]
    path = REVIEW_DIR / f"{key}.json"
 
    record = {
        "task_id": task_id,
        "queued_at": datetime.now(timezone.utc).isoformat(),
        "outcome": "declined",
        "prompt": prompt,
        "response_text": result.text,
        "stop_reason": result.stop_reason,
    }
    path.write_text(json.dumps(record, ensure_ascii=False, indent=2))
    return path
 
 
def handle(task_id: str, prompt: str, result: RunResult) -> str:
    outcome = classify(result)
    if outcome is Outcome.DECLINED:
        enqueue_for_review(task_id, prompt, result)
        return "held"          # 成功でも失敗でもなく「保留」
    if outcome is Outcome.INFRA_ERROR:
        raise TransientError(result.http_status)  # 再試行に回す
    if outcome is Outcome.DEGRADED:
        raise DefinitionOfDoneError(task_id)      # 完了条件で赤く
    return "ok"
 
 
class TransientError(Exception): ...
class DefinitionOfDoneError(Exception): ...

冪等キーで同じ入力の拒否を1件にまとめておくと、毎晩同じ依頼で断られてもキューが同じ内容で膨れません。週に一度キューをまとめて見返せば、断られた依頼にどんな傾向があるかも見えてきます。特定のトピックばかりが触れているなら、その領域は無人ではなく手元で扱う、という運用判断につなげられます。

この設計のもう一つの利点は、「保留」という第三の返り値を持つことで、パイプライン全体のログが正直になることです。拒否を成功に数えなければ成功率は実態を映し、拒否を失敗に数えなければ再試行やアラートを無駄に焚かずに済みます。長時間の無人運用に上限や退避先を用意しておく発想は、pause_turn の継続ループに上限を入れる — 無人で長時間サーバーツールを安全に回す とも地続きです。

まとめと次の一歩

安全上の拒否は、無人パイプラインにとって障害ではなく、想定しておくべき正常な分岐のひとつです。二分岐の成否判定に「保留」という三つ目を足し、拒否はインフラエラーと分けて判別し、自動で言い換えて押し通さず、レビューキューへ渡して人の判断に戻す——この四点を仕込んでおくだけで、静かにすり抜けていた結末が、翌朝きちんと目に入るようになります。

まずは手元の一番大切な定期処理を一つだけ選び、classify 相当の判別を1回の呼び出しに挟んでみてください。断りを一度でも「保留」として拾えたなら、その仕組みは他のタスクへ広げる価値があります。私自身、Dolice として複数サイトを個人開発で無人運用するなかで、この保留の置き場所を用意してから、夜間の処理を安心して任せられるようになりました。お読みいただきありがとうございました。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

◈ Cowork2026-08-23
コネクタが使えない朝に、待つのか人に返すのかを分ける三つの状態
無人実行でコネクタに届かないとき、原因は起動途中・未認証・恒久欠落の三つに分かれます。それぞれ正しい対処が正反対になる理由と、待機予算を切り分ける判定器の実装をまとめました。
◈ Cowork2026-05-04
Cowork スケジュールタスクが黙って止まる理由と、自動で立ち直る仕組みの作り方
Coworkのスケジュールタスクが黙って止まる原因を、実測した終了コードとエラー文で切り分けます。safe.directory が失敗を遅らせる仕組み、書ける場所を先に決めるプリフライト、理由コード付きログまで扱います。
◈ Cowork2026-09-08
毒入りサンプルを置くまで、無人の検査は一度も落ちていませんでした
無人スケジュールで回している検査が、ログ整形のパイプ一本で終了コードを失っていました。消える経路を手元で測って切り分け、検査自身を検査する小さなハーネスまで組みます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます