What
Tool failures come back as successful MCP results, and the field that carries the
failure differs from tool to tool. A consumer (PostToolUse hook, telemetry, retry
logic, any automation) cannot tell success from failure generically — it has to
special-case every tool.
Observed over a full workshop day on one training VM: 733 tool calls, 38 genuine tool
failures, none of them flagged as an error.
Expected
A tool failure sets isError: true on the MCP CallToolResult, and the payload uses one
consistent shape across tools.
Actual
Every failure returns a normal result. The message lives in a different field per tool:
| Tool |
Failure shape |
Message field |
error_code? |
Count |
iris_query |
{"error": …, "error_code": "SQL_ERROR"} |
error |
yes |
16 |
iris_execute |
{"error_code": "IRIS_RUNTIME_ERROR", "success": false, "output": …} |
output |
yes |
12 |
iris_production |
{"error": …, "error_code": "INTEROP_ERROR"} |
error |
yes |
5 |
iris_doc (compile) |
{"success": false, "compile_console": [...], "compile_errors": [...]} |
compile_console |
no |
4 |
iris_test |
{"error_code": "NO_TESTS_FOUND", …} |
error |
yes |
1 |
Three consequences:
isError is never set — 38/38.
success: false is not usable as a substitute, because iris_test legitimately
returns success: false for a red test, which is a valid outcome and not a tool
failure. Anything keying on success alone will count red tests as tool errors.
iris_doc compile failures carry no error_code at all, so they can't even be
classified by code.
Repro
Each of these returns a result with no error flag:
Secondary: hint coverage is uneven
hint is the field that teaches the model how to recover, and it's the thing that
actually shortens the retry loop. 22 of 38 failures had no hint:
| Code |
with hint |
without |
iris_query:SQL_ERROR |
10 |
6 |
iris_execute:IRIS_RUNTIME_ERROR |
5 |
7 |
iris_production:INTEROP_ERROR |
0 |
5 |
iris_doc (compile) |
0 |
4 |
INTEROP_ERROR never carries one. The ErrProductionNotShutdownCleanly case above is a
good example: the fix is well-known and mechanical, but nothing in the response says so,
and the error names a different production than the one the caller asked to start,
which reads as a contradiction without a hint to explain it.
Impact
The model itself copes — it reads the message text and usually reasons its way out, so
this is not a runtime blocker. The cost lands on everything around the model:
- PostToolUse hooks and guards can't branch on "did this call fail".
- Retry/backoff logic has to hardcode per-tool field names.
- Any measurement of failure rates silently undercounts. (This surfaced while building
telemetry over workshop transcripts: keying on the standard error flag reported 8
failures where there were 46.)
Suggested fix
- Set
isError: true on the CallToolResult for genuine tool failures — explicitly
not for a red iris_test run, which is a valid outcome.
- Normalize one envelope across tools, e.g.
{success, error_code, error, hint}, keeping
output / compile_console / compile_errors as extra detail rather than as the
only place the message appears.
- Give
iris_doc compile failures an error_code (e.g. COMPILE_ERROR).
- Add
hint for INTEROP_ERROR and for compile failures.
Environment
- MCP:
0.6.22 — the binary self-reports iris-agentic-dev 0.6.22, and check_config
returns "mcp_version": "0.6.22". Built from tag v0.6.22-interop.
- Client: Claude Code +
iris-interop-skills 1.5.9.
- Host:
IRIS for Windows (x86-64) 2026.1 (Build 235U), MCP over http.
- Evidence: 9 sessions / 733 tool calls from one training VM (TR-26014 workshop).
Note on which code this is. 0.6.22 is the newest tagged code, despite the
repo's "latest release" being v0.7.0-interop: that tag points at commit 2081c4a
(2026-06-19), whose Cargo.toml reads version = "0.6.14", and v0.6.22-interop is
16 commits ahead of it and 0 behind. So this report is against the newest code, not
against something already superseded — but the release labelling is worth a look
separately.
What
Tool failures come back as successful MCP results, and the field that carries the
failure differs from tool to tool. A consumer (PostToolUse hook, telemetry, retry
logic, any automation) cannot tell success from failure generically — it has to
special-case every tool.
Observed over a full workshop day on one training VM: 733 tool calls, 38 genuine tool
failures, none of them flagged as an error.
Expected
A tool failure sets
isError: trueon the MCPCallToolResult, and the payload uses oneconsistent shape across tools.
Actual
Every failure returns a normal result. The message lives in a different field per tool:
error_code?iris_query{"error": …, "error_code": "SQL_ERROR"}erroriris_execute{"error_code": "IRIS_RUNTIME_ERROR", "success": false, "output": …}outputiris_production{"error": …, "error_code": "INTEROP_ERROR"}erroriris_doc(compile){"success": false, "compile_console": [...], "compile_errors": [...]}compile_consoleiris_test{"error_code": "NO_TESTS_FOUND", …}errorThree consequences:
isErroris never set — 38/38.success: falseis not usable as a substitute, becauseiris_testlegitimatelyreturns
success: falsefor a red test, which is a valid outcome and not a toolfailure. Anything keying on
successalone will count red tests as tool errors.iris_doccompile failures carry noerror_codeat all, so they can't even beclassified by code.
Repro
Each of these returns a result with no error flag:
Secondary:
hintcoverage is unevenhintis the field that teaches the model how to recover, and it's the thing thatactually shortens the retry loop. 22 of 38 failures had no
hint:iris_query:SQL_ERRORiris_execute:IRIS_RUNTIME_ERRORiris_production:INTEROP_ERRORiris_doc(compile)INTEROP_ERRORnever carries one. TheErrProductionNotShutdownCleanlycase above is agood example: the fix is well-known and mechanical, but nothing in the response says so,
and the error names a different production than the one the caller asked to start,
which reads as a contradiction without a hint to explain it.
Impact
The model itself copes — it reads the message text and usually reasons its way out, so
this is not a runtime blocker. The cost lands on everything around the model:
telemetry over workshop transcripts: keying on the standard error flag reported 8
failures where there were 46.)
Suggested fix
isError: trueon theCallToolResultfor genuine tool failures — explicitlynot for a red
iris_testrun, which is a valid outcome.{success, error_code, error, hint}, keepingoutput/compile_console/compile_errorsas extra detail rather than as theonly place the message appears.
iris_doccompile failures anerror_code(e.g.COMPILE_ERROR).hintforINTEROP_ERRORand for compile failures.Environment
0.6.22— the binary self-reportsiris-agentic-dev 0.6.22, andcheck_configreturns
"mcp_version": "0.6.22". Built from tagv0.6.22-interop.iris-interop-skills1.5.9.IRIS for Windows (x86-64) 2026.1 (Build 235U), MCP overhttp.