Blog • Claude Code / MCP
Après Claude Code 2.1.310, comment écrire en JSON le délai d'une elicitation MCP sans interaction pour ne pas rappeler le même outil ?
Le 23 octobre 2026, Claude Code 2.1.310 a changé une erreur sans interaction. Si un appel MCP expire alors que l'elicitation du serveur est sans réponse, le modèle apprend qu'aucune réponse n'est venue et qu'il ne faut pas répéter l'appel.
La doc MCP du même jour : si une tâche demande une entrée sans interaction, Claude Code demande au serveur d'annuler, sans hook Elicitation ni callback onElicitation du Agent SDK. Allonger timeout ne fait pas apparaître quelqu'un pour répondre. Le prochain claude -p rappelle l'outil ou non selon le JSON enregistré.
L'arrêt du journal quand OAuth ne correspond pas est dans l'arrêt OAuth. Un serveur stdio rechargé par la liste d'autorisation est dans la liste env. Rejouer un item entre fournisseurs est une autre enveloppe, dans le rejeu Open Responses. Cet article ne couvre que les champs de journal après un délai d'elicitation sans interaction.
- Le timeout sans interaction de 2.1.310 dit de ne pas rappeler. Ce n'est pas un timeout à réessayer.
- Si status vaut input_required sans interaction, la doc demande l'annulation, sans hook Elicitation.
- Le journal local sépare avec reason une elicitation sans réponse, un timeout et outcome unknown. Si repeat vaut true, le garde refuse les deux premiers.
- N'appliquez pas le même repeat false à outcome unknown. La doc n'autorise un second appel qu'après avoir su ce que le serveur a déjà fait.
Gardez ceci : 2.1.310 sépare l'absence de réponse d'un nouvel essai. La suite suit status, reason, puis repeat, et quel champ arrête l'appel suivant dans un journal sans interaction.
2.1.310 sort une elicitation sans réponse des timeouts à réessayer
Le changement est dans le journal des modifications Claude Code, les notes de version sur v2.1.310. En clair : sans interaction, un appel MCP qui expire pendant une elicitation sans réponse dit maintenant au modèle qu'aucune réponse n'est venue et de ne pas répéter l'appel. Un terminal interactif où quelqu'un peut remplir le formulaire n'utilise pas cette règle.
La doc de connexion MCP écrit le timeout de chaque serveur en millisecondes. Une valeur sous 1000 est ignorée, et les notifications de progrès n'allongent pas cette horloge. Sans interaction, pas de dialogue d'elicitation : 120000 attend plus longtemps une réponse qui ne viendra pas.
{
"mcpServers": {
"reports": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"timeout": 120000
}
}
}
Une chaîne claude -p en un coup ne devient pas non plus une tâche MCP. La doc dit que cette exécution n'obtient pas de tâche ; la suivre demande une reprise interactive ou --input-format stream-json. Un délai d'elicitation peut viser un appel d'outil ordinaire, le journal ne doit pas exiger taskId.
Quels champs sont obligatoires quand le serveur renvoie une tâche qui demande une entrée ?
Dans la révision de protocole 2026-07-28, un long travail peut répondre à tools/call par une tâche. Les champs suivent l'extension MCP tasks, sur l'extension tasks. La doc Claude Code exige resultType task, ainsi que taskId, status, createdAt, lastUpdatedAt et ttlMs. status ne vaut que working, input_required, completed, failed ou cancelled. input_required porte inputRequests, et elicitation/create n'est servi que dans un terminal interactif. Sans interaction, Claude Code demande au serveur d'annuler.
{
"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": {}
}
}
}
Ce JSON est le reçu du serveur, pas un interrupteur de nouvel essai. Une exécution sans interaction qui voit input_required doit annuler, pas coller le même objet dans le prochain claude -p pour le relancer.
Comment le journal local dit-il de ne pas rappeler ?
Ne mettez pas repeat dans le status du protocole. repeat et reason sont vos champs de journal. Sans interaction et sans personne pour répondre : reason elicitation_unanswered. Pour l'erreur de timeout de 2.1.310 : reason elicitation_timeout. Les deux mettent repeat à false. taskId peut manquer, car un appel d'outil ordinaire n'est pas toujours une tâche.
{
"session": "non_interactive",
"tool": "export_report",
"taskId": "export-7f3a9c2e",
"status": "input_required",
"reason": "elicitation_unanswered",
"repeat": false
}
Le schema ci-dessous fixe la forme. session vaut seulement interactive ou non_interactive. status n'utilise que les cinq valeurs du protocole. reason sépare l'arrêt d'outcome unknown. additionalProperties false empêche de glisser tout le texte d'erreur dans un champ non déclaré.
{
"$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" }
}
}
Pourquoi refuser repeat true après un schema valide ?
Le schema ci-dessus exige seulement que repeat soit un booléen. Le journal ci-dessous a tous les champs et des types légaux, mais la session est non interactive, reason vaut elicitation_unanswered et repeat vaut true. Le garde doit renvoyer repeat_conflict, et l'exécution suivante sans interaction n'envoie pas cet appel.
{
"session": "non_interactive",
"tool": "export_report",
"taskId": "export-7f3a9c2e",
"status": "input_required",
"reason": "elicitation_unanswered",
"repeat": true
}
| Journal vu | Garde | Exécution suivante sans interaction |
|---|---|---|
| Non interactif, input_required, repeat false | stop | Ne pas envoyer cet appel d'outil. |
| Le même cas sans réponse, mais repeat true | repeat_conflict | Rejeter. Mettre d'abord repeat à false. |
| reason vaut elicitation_timeout, repeat false | stop | Le timeout de 2.1.310 ne se rappelle pas non plus. |
| session vaut interactive | not_headless | Ne pas appliquer cet arrêt sans interaction. Une session interactive peut répondre à l'elicitation. |
Pourquoi outcome unknown n'est-il pas le même arrêt ?
La doc décrit une autre ligne de transcript : après un refus de l'identifiant de tâche ou une autre erreur permanente, Claude Code cesse de suivre et écrit outcome unknown. La suite dit qu'un second appel peut refaire le travail, donc il faut d'abord demander au serveur ce qui a déjà été fait. Ce n'est pas le ne pas rappeler de 2.1.310.
Le garde vérifie le schema, puis si la session est non interactive. reason outcome_unknown renvoie check_server et n'est pas plié en stop. Elicitation sans réponse, elicitation_timeout et input_required sans interaction exigent repeat false et renvoient stop. Les autres combinaisons renvoient no_rule. Cette fonction ne décide pas une nouvelle tâche métier.
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"
Comment séparer l'arrêt et le résultat inconnu dans JSONNote ?
- 1D'abord la forme avec le schema
Ouvrez JSON Schema. Schema de journal au premier champ, journal d'arrêt ou celui avec repeat true au second. Un tool manquant ou un mauvais reason est signalé d'abord. repeat true peut passer, le garde compte encore.
- 2Puis voir si repeat est un booléen
Ouvrez JSON Format. Collez le journal entier. Si repeat est la chaîne false et non le booléen false, le schema échoue et le garde renvoie schema. Ne le tenez pas pour déjà arrêté.
- 3Enfin comparer le reçu de tâche et le journal local
Ouvrez JSON Diff. À gauche la tâche input_required du serveur, à droite le journal local. reason et repeat seulement à droite sont l'arrêt. Si aucun côté n'a repeat, l'exécution suivante sans interaction n'a encore rien qui arrête l'appel.
Questions fréquentes
Si j'allonge timeout à quelques minutes, une elicitation sans interaction peut-elle attendre une réponse ?
Non. Sans interaction, personne ne remplit l'elicitation. Un timeout plus long allonge seulement l'attente d'une réponse qui ne vient pas. L'erreur de 2.1.310 dit au modèle de ne pas rappeler. Elle ne transforme pas ce délai en nouvel essai automatique.
Peut-on aussi enregistrer outcome unknown avec repeat false ?
N'utilisez pas le même arrêt. Pour outcome unknown, la doc dit qu'un second appel peut refaire un travail déjà fait, donc interrogez le serveur avant de rappeler. Le garde renvoie check_server pour ce reason. Le délai d'elicitation est le stop.
Un claude -p en un coup entre-t-il dans une tâche MCP ?
La doc dit non. Cette exécution n'obtient pas de tâche MCP. Pour suivre une tâche, reprenez en interactif ou utilisez --input-format stream-json et les conditions de mise en arrière-plan automatique. Un délai d'elicitation peut viser un appel ordinaire, taskId n'est pas obligatoire dans le journal.
Faut-il aussi journaliser input_required dans un terminal interactif comme stop ?
Non. Cet arrêt ne couvre que le non-interactif. Un terminal interactif peut répondre à elicitation/create. La doc dit aussi que l'annulation sans interaction ne lance ni hook Elicitation ni onElicitation. N'appliquez pas le repeat false headless à une session interactive.
Faut-il écrire repeat false dans le status du protocole ?
Non. Le status du protocole reste working, input_required, completed, failed ou cancelled. repeat et reason restent dans le journal local. Mettre false dans status ne correspond plus à l'extension de tâches et cache s'il s'agissait d'un délai d'elicitation ou d'outcome unknown.
Conclusion
2.1.310 change le délai où personne n'a répondu dans une session sans interaction. Ce n'est pas une politique de nouvel essai pour chaque erreur MCP.
Décision : sans interaction, elicitation sans réponse ou timeout, journalisez repeat false et arrêtez l'appel suivant ; refusez repeat true même si le schema passe ; pour outcome unknown, interrogez d'abord le serveur et n'utilisez pas le même arrêt.
Ensuite : forme du journal avec Schema, repeat booléen avec Format, reçu de tâche et arrêt local avec Diff.