Blog • Claude Code / MCP
Nach Claude Code 2.1.310: wie schreibt man ein MCP-Rückfrage-Timeout in einer nicht interaktiven Sitzung als JSON, damit dasselbe Tool nicht erneut aufgerufen wird?
Am 23. Oktober 2026 änderte Claude Code 2.1.310 einen Fehler ohne Interaktion. Läuft ein MCP-Aufruf ab, während die Rückfrage des Servers unbeantwortet ist, erfährt das Modell: keine Antwort kam, diesen Aufruf nicht wiederholen.
Die MCP-Doku desselben Tags: fordert eine Aufgabe in einer Sitzung ohne Interaktion Eingabe an, bittet Claude Code den Server um Abbruch und führt weder Elicitation-Hooks noch onElicitation des Agent SDK aus. Ein größeres timeout erzeugt niemanden, der antwortet. Ob das nächste claude -p das Tool erneut ruft, steht im gespeicherten JSON.
Stopp im Log bei abweichendem OAuth steht in dem OAuth-Stopp. Ein stdio-Server, den die Allowlist wieder lädt, steht in der env-Allowlist. Ein Item über Anbieter zu wiederholen ist ein anderer Umschlag, in dem Open-Responses-Replay. Dieser Artikel behandelt nur die Logfelder nach einem Rückfrage-Timeout ohne Interaktion.
- Das Timeout ohne Interaktion in 2.1.310 sagt: nicht erneut aufrufen. Es ist kein Timeout zum Wiederholen.
- Ist status input_required ohne Interaktion, verlangt die Doku Abbruch und keine Elicitation-Hooks.
- Das lokale Log trennt mit reason unbeantwortete Rückfrage, Timeout und outcome unknown. Ist repeat true, lehnt das Gate die ersten beiden ab.
- Für outcome unknown nicht dasselbe repeat false setzen. Die Doku erlaubt einen zweiten Aufruf erst, nachdem klar ist, was der Server schon tat.
Zuerst dies: 2.1.310 trennt keine Antwort von erneut versuchen. Unten der Reihe nach status, reason, repeat, und welches Feld den nächsten Tool-Aufruf im Log ohne Interaktion stoppt.
2.1.310 löst eine unbeantwortete Rückfrage aus wiederholbaren Timeouts
Die Änderung steht im Claude-Code-Changelog, die Release-Notes unter v2.1.310. Gemeint ist: in einer Sitzung ohne Interaktion sagt der Fehler bei einem MCP-Aufruf, der während einer unbeantworteten Rückfrage abläuft, dem Modell, dass keine Antwort kam und der Aufruf nicht wiederholt werden soll. Ein interaktives Terminal, in dem jemand das Formular füllt, nutzt diese Regel nicht.
Die am selben Tag aktualisierte MCP-Verbindungsdoku schreibt timeout je Server in Millisekunden. Werte unter 1000 werden ignoriert, Fortschrittsmeldungen verlängern diese Wanduhr nicht. Ohne Interaktion gibt es keinen Rückfrage-Dialog, also wartet 120000 nur länger auf eine Antwort, die nicht kommt.
{
"mcpServers": {
"reports": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"timeout": 120000
}
}
}
Auch ein einmaliges claude -p wird kein MCP-Task. Die Doku sagt, dieser Lauf bekommt keine Tasks; zum Verfolgen braucht es eine interaktive Wiederaufnahme oder --input-format stream-json. Ein Rückfrage-Timeout kann auch einen gewöhnlichen Tool-Aufruf treffen, das Log darf taskId nicht verlangen.
Welche Felder sind Pflicht, wenn der Server eine Aufgabe mit Eingabebedarf zurückgibt?
In Protokollrevision 2026-07-28 kann ein langer Job tools/call als Task beantworten. Die Felder folgen der MCP-Tasks-Erweiterung, unter der Tasks-Erweiterung. Die Claude-Code-Doku verlangt resultType task sowie taskId, status, createdAt, lastUpdatedAt und ttlMs. status ist nur working, input_required, completed, failed oder cancelled. input_required trägt inputRequests, und elicitation/create wird nur im interaktiven Terminal bedient. Ohne Interaktion geht das nicht, der Server soll abbrechen.
{
"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": {}
}
}
}
Dieses JSON ist die Quittung des Servers, kein Wiederholen-Schalter. Ein Lauf ohne Interaktion, der input_required sieht, soll abbrechen, nicht dasselbe Objekt in das nächste claude -p kleben und erneut ausführen.
Wie sagt das lokale Log, nicht erneut aufzurufen?
repeat gehört nicht in den Protokoll-status. repeat und reason sind eigene Logfelder. Ohne Interaktion und ohne Antwort: reason elicitation_unanswered. Für den Timeout-Fehler von 2.1.310: reason elicitation_timeout. Beide setzen repeat auf false. taskId darf fehlen, denn ein gewöhnlicher Tool-Aufruf ist nicht immer ein Task.
{
"session": "non_interactive",
"tool": "export_report",
"taskId": "export-7f3a9c2e",
"status": "input_required",
"reason": "elicitation_unanswered",
"repeat": false
}
Die Form fixiert das Schema unten. session ist nur interactive oder non_interactive. status nutzt nur die fünf Protokollwerte. reason trennt den Stopp von outcome unknown. additionalProperties false verhindert, dass der ganze Fehlertext in ein nicht deklariertes Feld wandert.
{
"$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" }
}
}
Warum repeat true nach bestandenem Schema ablehnen?
Das Schema oben verlangt nur, dass repeat ein Boolean ist. Das Log unten hat alle Felder und legale Typen, aber die Sitzung ist nicht interaktiv, reason ist elicitation_unanswered und repeat ist true. Das Gate soll repeat_conflict liefern, der nächste Lauf ohne Interaktion sendet diesen Aufruf nicht.
{
"session": "non_interactive",
"tool": "export_report",
"taskId": "export-7f3a9c2e",
"status": "input_required",
"reason": "elicitation_unanswered",
"repeat": true
}
| Sichtbares Log | Gate | Nächster Lauf ohne Interaktion |
|---|---|---|
| Nicht interaktiv, input_required, repeat false | stop | Diesen Tool-Aufruf nicht senden. |
| Dieselbe unbeantwortete Lage, aber repeat true | repeat_conflict | Ablehnen. Zuerst repeat auf false setzen. |
| reason ist elicitation_timeout, repeat false | stop | Auch das Timeout von 2.1.310 nicht erneut aufrufen. |
| session ist interactive | not_headless | Diesen Stopp ohne Interaktion nicht anwenden. Eine interaktive Sitzung kann die Rückfrage beantworten. |
Warum ist outcome unknown nicht derselbe Stopp?
Die Doku nennt eine andere Transcript-Zeile: lehnt der Server die Task-ID ab oder gibt einen anderen dauerhaften Fehler, hört Claude Code auf zu folgen und schreibt outcome unknown. Danach kann ein zweiter Aufruf die Arbeit wiederholen, also vorher fragen, was der Server schon tat. Das ist nicht dasselbe wie nicht erneut aufrufen in 2.1.310.
Das Gate prüft das Schema, dann ob die Sitzung nicht interaktiv ist. reason outcome_unknown liefert check_server und wird nicht zu stop zusammengelegt. Unbeantwortete Rückfrage, elicitation_timeout und input_required ohne Interaktion verlangen repeat false und liefern stop. Andere Kombinationen liefern no_rule. Die Funktion entscheidet keine neue fachliche Aufgabe.
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"
Wie trennt man in JSONNote den Stopp vom unbekannten Ergebnis?
- 1Zuerst die Form per Schema
Öffnen Sie JSON Schema. Log-Schema ins erste Feld, Stopp-Log oder das mit repeat true ins zweite. Fehlendes tool oder falscher reason wird zuerst markiert. repeat true kann bestehen, das Gate bleibt nötig.
- 2Dann prüfen, ob repeat ein Boolean ist
Öffnen Sie JSON formatieren. Das ganze Log einfügen. Ist repeat die Zeichenkette false statt des Booleans false, scheitert das Schema und das Gate liefert schema. Das gilt nicht als schon gestoppt.
- 3Zuletzt Quittung und lokales Log vergleichen
Öffnen Sie JSON Diff. Links die input_required-Aufgabe des Servers, rechts das lokale Log. reason und repeat nur rechts sind der Stopp. Fehlt repeat auf beiden Seiten, hat der nächste Lauf ohne Interaktion noch keinen Grund zu stoppen.
Häufige Fragen
Wartet eine Rückfrage ohne Interaktion auf eine Antwort, wenn timeout mehrere Minuten beträgt?
Nein. In einer Sitzung ohne Interaktion füllt niemand die Rückfrage aus. Ein längeres timeout streckt nur das Warten auf eine Antwort, die nicht kommt. Der Fehler von 2.1.310 sagt dem Modell, nicht erneut aufzurufen. Er macht aus dem Timeout keinen automatischen nächsten Versuch.
Darf outcome unknown auch direkt als repeat false gespeichert werden?
Nicht denselben Stopp verwenden. Zu outcome unknown sagt die Doku, ein zweiter Aufruf könne schon erledigte Arbeit wiederholen, also zuerst den Server fragen. Das Gate liefert für diesen reason check_server. Das Rückfrage-Timeout ist der Stopp.
Geht ein einmaliges claude -p in einen MCP-Task?
Die Doku sagt nein. Dieser Lauf bekommt keine MCP-Tasks. Zum Verfolgen interaktiv fortsetzen oder --input-format stream-json nutzen und die Bedingungen für automatisches Background erfüllen. Ein Rückfrage-Timeout kann auch einen gewöhnlichen Aufruf treffen, taskId ist im Log nicht Pflicht.
Soll input_required im interaktiven Terminal auch als stop geloggt werden?
Nein. Dieser Stopp gilt nur ohne Interaktion. Ein interaktives Terminal kann elicitation/create beantworten. Die Doku sagt auch, dass der Abbruch ohne Interaktion keine Elicitation-Hooks und kein onElicitation ausführt. repeat false aus dem Headless-Lauf nicht auf eine interaktive Sitzung anwenden.
Soll repeat false in den Protokoll-status geschrieben werden?
Nein. Protokoll-status bleibt working, input_required, completed, failed oder cancelled. repeat und reason bleiben im lokalen Log. false im status passt nicht zur Task-Erweiterung und verdeckt, ob es ein Rückfrage-Timeout oder outcome unknown war.
Fazit
2.1.310 ändert das Timeout, bei dem in einer Sitzung ohne Interaktion niemand geantwortet hat. Es ist keine Wiederholungsregel für jeden MCP-Fehler.
Entscheidung: ohne Interaktion bei unbeantworteter Rückfrage oder Timeout repeat false loggen und den nächsten Aufruf stoppen; repeat true auch bei bestandenem Schema ablehnen; bei outcome unknown zuerst den Server fragen und nicht denselben Stopp verwenden.
Als Nächstes: Logform per Schema, repeat per Format als Boolean prüfen, Quittung und lokalen Stopp per Diff vergleichen.