ブログ • 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 が input_required なら、文書はタスクのキャンセルを求め、Elicitation hook は走らない。
- ローカルログは reason で未回答、タイムアウト、outcome unknown を分ける。repeat が true なら前二者はゲートが拒否する。
- outcome unknown に同じ repeat false を当てはめない。文書は、サーバが何をしたか確認したあとで二度目を許す。
先にこれだけ:2.1.310 は未回答と再試行を分けた。以下は status、reason、repeat の順で、非対話ログのどの欄が次のツール呼び出しを止めるかを見る。
2.1.310 は未回答を再試行タイムアウトから外した
変更は Claude Code の更新履歴、リリースノートは v2.1.310。意味は、非対話セッションで確認が未回答のままタイムアウトした MCP ツール呼び出しについて、エラーがモデルに未回答と再呼び出し禁止を伝える、ということ。フォームを埋められる対話端末はこの規則を使わない。
同日更新の MCP 接続文書 はサーバごとの timeout をミリ秒で書く。1000 未満は無視され、進捗通知もこの壁時計を延ばさない。非対話では確認ダイアログを出せないので、120000 は現れない回答をより長く待つだけ。
{
"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 は対話端末だけで処理する。非対話では処理できず、サーバにキャンセルを頼む。
{
"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 は無くてよい。
{
"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": "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 を返し、次の非対話実行はこのツール呼び出しを送らない。
{
"session": "non_interactive",
"tool": "export_report",
"taskId": "export-7f3a9c2e",
"status": "input_required",
"reason": "elicitation_unanswered",
"repeat": true
}
| 見ているログ | ゲート | 次の非対話実行 |
|---|---|---|
| 非対話、input_required、repeat false | stop | このツール呼び出しを送らない。 |
| 同じ未回答だが repeat が true | repeat_conflict | 拒否。先に repeat を false にする。 |
| reason が elicitation_timeout、repeat は false | stop | 2.1.310 のタイムアウトも再呼び出ししない。 |
| session が interactive | not_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。この関数は新しい業務タスクを代わりに決めない。
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先に schema で形を見る
JSON Schema。1欄にログ schema、2欄に停止ログか repeat true のログ。tool 欠落や reason の誤りは先に赤くなる。repeat true は通ることがあるので、ゲートも見る。
- 2次に repeat が真偽値か見る
JSON フォーマット。ログ全体を貼る。repeat が真偽値の false でなく文字列の false なら schema は失敗し、ゲートは schema を返す。既に停止したとはみなさない。
- 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 でタスク受領とローカル停止を比べる。