fix(workflow): answer when the product does not exist
MCP does not report a tool failure as a JSON-RPC error. It reports it as a successful result carrying isError: true, with the reason as text. The node only checked `error`, so it read the failure as data and went on to JSON.parse the sentence "HTTP call failed with status 404" as a product. The throw is what made the webhook answer nothing at all. Now checks result.isError, distinguishes a missing product from any other tool failure, and guards every parse of a payload it did not construct. Through the facade, escalate: true becomes a task in state failed rather than a completed one. Also corrects this file: the earlier note said the run completed in success with every node executing. It did not — that was the wrong execution. n8n's executions list did not return them newest-first as assumed, and the conclusion drawn from it sent the search to the wrong place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+35
-8
@@ -51,13 +51,40 @@ The regex is loose after the underscore on purpose — a reference that looks
|
||||
valid but does not exist must reach the catalog and come back as "no such
|
||||
product", because answering "you named no product" would be false.
|
||||
|
||||
### Known gap
|
||||
### The one that took longest
|
||||
|
||||
A product code that does not exist returns an **empty body** rather than that
|
||||
message. The run completes, every node executes including the one that composes
|
||||
the answer, and the response is still empty — so the fault is between the
|
||||
composing node and the webhook response, not in the decision logic.
|
||||
`alwaysOutputData` on the read node did not change it.
|
||||
A product code that does not exist returned an **empty body**, and the reason is
|
||||
worth keeping.
|
||||
|
||||
For an autonomous agent, silence is the worst possible answer. Not shipped as
|
||||
acceptable — recorded as unfinished.
|
||||
MCP does not report a tool failure as a JSON-RPC `error`. It reports it as a
|
||||
perfectly successful **result** carrying `isError: true`, with the reason as
|
||||
text:
|
||||
|
||||
```
|
||||
data: {"jsonrpc":"2.0","result":{"content":[{"type":"text",
|
||||
"text":"HTTP call failed with status 404: {\"detail\":\"Not found.\"}"}],
|
||||
"isError":true},"id":2}
|
||||
```
|
||||
|
||||
A client that only checks `rpc.error` therefore reads a failure as data. This
|
||||
one went on to `JSON.parse` the sentence "HTTP call failed with status 404" as
|
||||
if it were a product, threw, and the webhook answered nothing at all.
|
||||
|
||||
Two lessons, both now in the node:
|
||||
|
||||
- **check `result.isError`, not just `error`** — they mean different things: a
|
||||
protocol failure versus a tool that ran and failed;
|
||||
- **guard every `JSON.parse` of a payload you did not construct.** The throw is
|
||||
what turned a wrong answer into no answer, which for an autonomous agent is
|
||||
the worse of the two.
|
||||
|
||||
Now:
|
||||
|
||||
```
|
||||
"Peut-on promettre S99_NOPE ?"
|
||||
→ {"answer":"There is no product S99_NOPE in the catalog.","escalate":true}
|
||||
```
|
||||
|
||||
and through the A2A facade, `escalate: true` becomes a task in state **failed**
|
||||
rather than a completed one — the honest shape for "a human has to look at
|
||||
this".
|
||||
|
||||
Reference in New Issue
Block a user