ブログ • Claude Code / MCP

Claude Code 2.1.310 のあと、非対話セッションで MCP の確認がタイムアウトしたら、同じツールを再呼び出ししないことを JSON ログにどう残すか?

2026年10月23日、Claude Code 2.1.310 は非対話セッションのエラーを一つ変えた。サーバの確認が未回答のまま MCP ツール呼び出しがタイムアウトすると、回答は来ておらず同じ呼び出しを繰り返すなとモデルに伝える。

同日の MCP 文書は、非対話セッションでタスクが入力を求めたら Claude Code はサーバにキャンセルを頼み、Elicitation hook も Agent SDK の onElicitation も走らせないと書く。timeout を伸ばしても答える人は現れない。次の claude -p が同じツールを打つかは、保存した JSON で決まる。

OAuth が一致しないときの停止は OAuth の停止点。許可リストで stdio サーバが再ロードされる話は env 許可リスト。プロバイダをまたぐ item の再送は別の封筒で、Open Responses の再送。本記事は非対話の確認タイムアウト後のログ項目だけを書く。

先にこれだけ:2.1.310 は未回答と再試行を分けた。以下は status、reason、repeat の順で、非対話ログのどの欄が次のツール呼び出しを止めるかを見る。

2.1.310 は未回答を再試行タイムアウトから外した

変更は Claude Code の更新履歴、リリースノートは v2.1.310。意味は、非対話セッションで確認が未回答のままタイムアウトした MCP ツール呼び出しについて、エラーがモデルに未回答と再呼び出し禁止を伝える、ということ。フォームを埋められる対話端末はこの規則を使わない。

同日更新の MCP 接続文書 はサーバごとの timeout をミリ秒で書く。1000 未満は無視され、進捗通知もこの壁時計を延ばさない。非対話では確認ダイアログを出せないので、120000 は現れない回答をより長く待つだけ。

サーバ項目の timeout。未回答を埋めない。
{
  "mcpServers": {
    "reports": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "timeout": 120000
    }
  }
}

一回きりの claude -p 文字列も MCP task にはならない。文書はその実行がタスクを得ないとし、追跡には対話での再開か --input-format stream-json が要る。確認タイムアウトは通常のツール呼び出しにも起きるので、ログは taskId を必須にしない。

サーバが入力を求めるとき、返すタスク JSON の必須項目は?

プロトコル改訂 2026-07-28 では、長い仕事をタスクとして tools/call に返せる。項目は MCP tasks 拡張に従う。tasks 拡張。Claude Code の文書は resultType を task とし、taskId、status、createdAt、lastUpdatedAt、ttlMs を必須にする。status は working、input_required、completed、failed、cancelled のみ。input_required は inputRequests を伴い、elicitation/create は対話端末だけで処理する。非対話では処理できず、サーバにキャンセルを頼む。

サーバが返すタスク。status は input_required、メソッドは elicitation/create。
{
  "resultType": "task",
  "taskId": "export-7f3a9c2e",
  "status": "input_required",
  "createdAt": "2026-10-23T16:44:00Z",
  "lastUpdatedAt": "2026-10-23T16:44:00Z",
  "ttlMs": null,
  "inputRequests": {
    "confirm": {
      "method": "elicitation/create",
      "params": {}
    }
  }
}

この JSON はサーバの受領であり、再試行スイッチではない。非対話が input_required を見たらキャンセルし、同じオブジェクトを次の claude -p に貼って再実行しない。

ローカルログはどう再呼び出し禁止を書くか?

プロトコルの status に repeat を混ぜない。repeat と reason は自分のログ項目。非対話で答える人がいないときは reason を elicitation_unanswered にする。2.1.310 のタイムアウトエラーは elicitation_timeout。どちらも repeat は false。通常のツール呼び出しはタスクとは限らないので taskId は無くてよい。

次の非対話呼び出しを止められるログ。repeat は false。
{
  "session": "non_interactive",
  "tool": "export_report",
  "taskId": "export-7f3a9c2e",
  "status": "input_required",
  "reason": "elicitation_unanswered",
  "repeat": false
}

形は下の schema で固定する。session は interactive と non_interactive のみ。status はプロトコルの五値だけ。reason に停止と outcome unknown の区別を置く。additionalProperties は false で、モデルのエラー全文を未宣言項目へ入れない。

ローカルログの schema。repeat の型が合法でも再呼び出し可とは限らない。
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["session", "tool", "status", "reason", "repeat"],
  "properties": {
    "session": { "enum": ["interactive", "non_interactive"] },
    "tool": { "type": "string", "minLength": 1 },
    "taskId": { "type": "string" },
    "status": {
      "enum": ["working", "input_required", "completed", "failed", "cancelled"]
    },
    "reason": {
      "enum": ["elicitation_unanswered", "elicitation_timeout", "outcome_unknown", "none"]
    },
    "repeat": { "type": "boolean" }
  }
}

schema 通過後も、なぜ repeat true を拒否するか?

上の schema は repeat が真偽値であることだけを求める。下のログは項目と型は揃うが、非対話で reason が elicitation_unanswered、repeat が true。ゲートは repeat_conflict を返し、次の非対話実行はこのツール呼び出しを送らない。

schema は通る。ゲートは repeat_conflict を返すべき。
{
  "session": "non_interactive",
  "tool": "export_report",
  "taskId": "export-7f3a9c2e",
  "status": "input_required",
  "reason": "elicitation_unanswered",
  "repeat": true
}
見ているログゲート次の非対話実行
非対話、input_required、repeat falsestopこのツール呼び出しを送らない。
同じ未回答だが repeat が truerepeat_conflict拒否。先に repeat を false にする。
reason が elicitation_timeout、repeat は falsestop2.1.310 のタイムアウトも再呼び出ししない。
session が interactivenot_headlessこの非対話の停止は使わない。対話セッションは確認に答えられる。

なぜ outcome unknown を同じ停止にしないか?

文書には別の transcript がある。タスク ID をサーバが拒否するか恒久エラーのあと、Claude Code は追跡をやめ outcome unknown と書く。その続きは、二度目の呼び出しが仕事を繰り返すかもしれないので、再呼び出しの前にサーバが何をしたか確認する、というもの。2.1.310 の再呼び出し禁止とは別。

ゲートは schema のあと、非対話かを見る。reason が outcome_unknown なら check_server を返し、stop に畳まない。未回答、elicitation_timeout、非対話の input_required だけが repeat false を要求して stop を返す。それ以外は no_rule。この関数は新しい業務タスクを代わりに決めない。

非対話ログのゲート。check_server と stop を一つの真偽値にしない。
def actual_type(data: object) -> str:
    if data is None:
        return "null"
    if isinstance(data, bool):
        return "boolean"
    if isinstance(data, list):
        return "array"
    if isinstance(data, int):
        return "integer"
    if isinstance(data, float):
        return "number"
    if isinstance(data, str):
        return "string"
    if isinstance(data, dict):
        return "object"
    raise TypeError(type(data))

def validate(data: object, schema: dict) -> list[str]:
    errors: list[str] = []
    if not isinstance(schema, dict):
        return errors
    types = schema.get("type")
    if isinstance(types, str):
        types = [types]
    actual = actual_type(data)
    if types and not any(t == actual or (t == "number" and actual == "integer") for t in types):
        errors.append("type")
    if "enum" in schema and not any(value == data for value in schema["enum"]):
        errors.append("enum")
    if isinstance(data, str) and "minLength" in schema and len(data) < schema["minLength"]:
        errors.append("minLength")
    if isinstance(data, dict) and schema.get("properties"):
        for key in schema.get("required") or []:
            if key not in data:
                errors.append(f"required:{key}")
        if schema.get("additionalProperties") is False:
            for key in data:
                if key not in schema["properties"]:
                    errors.append(f"additional:{key}")
        for key, sub in schema["properties"].items():
            if key in data and data[key] is not None:
                errors.extend(validate(data[key], sub))
    return errors

def headless_repeat(record: dict, schema: dict) -> str:
    if validate(record, schema):
        return "schema"
    if record.get("session") != "non_interactive":
        return "not_headless"
    if record.get("reason") == "outcome_unknown":
        return "check_server"
    if record.get("reason") in {"elicitation_unanswered", "elicitation_timeout"} or record.get("status") == "input_required":
        if record.get("repeat") is not False:
            return "repeat_conflict"
        return "stop"
    return "no_rule"

JSONNote で停止と未知の結果をどう分けるか?

  1. 1
    先に schema で形を見る

    JSON Schema。1欄にログ schema、2欄に停止ログか repeat true のログ。tool 欠落や reason の誤りは先に赤くなる。repeat true は通ることがあるので、ゲートも見る。

  2. 2
    次に repeat が真偽値か見る

    JSON フォーマット。ログ全体を貼る。repeat が真偽値の false でなく文字列の false なら schema は失敗し、ゲートは schema を返す。既に停止したとはみなさない。

  3. 3
    最後にタスク受領とローカルログを比較

    JSON Diff。左にサーバの input_required タスク、右にローカルログ。右にだけ出る reason と repeat が停止。両方に repeat が無ければ、次の非対話実行を止める根拠がまだ無い。

よくある質問

timeout を数分にすれば、非対話の確認は回答を待てるか?

できない。非対話セッションには確認を埋める人がいない。長い timeout は来ない回答の待ち時間を延ばすだけ。2.1.310 のエラーは再呼び出しするなと伝えるもので、このタイムアウトを次回の自動再試行にはしない。

outcome unknown もそのまま repeat false にしてよいか?

同じ停止は使わない。文書は outcome unknown について、二度目が既にした仕事を繰り返すかもしれないので、再呼び出しの前にサーバへ確認すると書く。ゲートはこの reason に check_server を返す。確認タイムアウトの方が stop。

一回きりの claude -p は MCP task に入るか?

文書は入らないと書く。その実行は MCP task を得ない。タスクを追うには対話で再開するか、--input-format stream-json と自動バックグラウンドの条件が要る。確認タイムアウトは通常のツール呼び出しでも起きるので、ログの taskId は必須ではない。

対話端末の input_required も stop と書くか?

しない。この停止は非対話だけを覆う。対話端末は elicitation/create に答えられる。文書は、非対話でタスクをキャンセルするとき Elicitation hook も onElicitation も走らないと書く。対話セッションに headless の repeat false を当てはめない。

repeat false をプロトコルの status に書くか?

しない。プロトコルの status は working、input_required、completed、failed、cancelled のまま。repeat と reason はローカルログに残す。false を status に入れるとタスク拡張に合わず、確認タイムアウトか outcome unknown かも分からなくなる。

まとめ

2.1.310 が変えたのは、非対話セッションで誰も答えなかったタイムアウトであり、すべての MCP エラーの再試行方針ではない。

結論:非対話で確認が未回答またはタイムアウトなら、ログに repeat false を書いて次の呼び出しを止める。repeat true は schema が通っても拒否する。outcome unknown は先にサーバへ確認し、同じ停止を使わない。

次:Schema でログの形を見て、フォーマットで repeat が真偽値か確認し、Diff でタスク受領とローカル停止を比べる。

ブログへ戻る