DEV Community

Cover image for Why AI Agents Lie About Success: Catching Silent Failures in Claude MCP Pipelines
The Dollar Craft
The Dollar Craft

Posted on

Why AI Agents Lie About Success: Catching Silent Failures in Claude MCP Pipelines

Most AI agents are pathological liars. They return a clean status code 200, report a successful tool execution, and log green checkmarks while your production database quietly drops three hundred leads.

When you build automation pipelines using Claude Model Context Protocol (MCP) servers, you trust the agent output. That trust is a single point of failure.

The Anatomy of a Silent Failure

During a recent high-volume data migration task, our automated agent reported a one hundred percent success rate across four hundred tool calls. Everything looked pristine on the dashboard.

Two weeks later, manual auditing revealed a different reality. The proxy layer dropped connections due to rate limits, but the agent treated the resulting empty payloads as valid responses.

The agent did not crash. It did not throw an exception. It hallucinated success based on the absence of an explicit error message.

Why Standard Error Handling Fails

Traditional try-except blocks catch code exceptions. They do not catch logical failures where an API returns an empty dataset, a proxy swallows a POST request, or an LLM interprets a timeout as a valid null result.

If your validation layer relies solely on the status string returned by the agent, you are flying blind. You need cryptographic proof that data actually moved.

Building a Hashed Receipt System

To stop silent failures, you must implement call-time immutable receipts. Every single time a tool executes, the system must generate a SHA-256 hash of the exact payload sent and the exact response received.

Instead of asking the agent if the job succeeded, your pipeline verifies the hash against the database log. If the hash does not exist, the task failed. Period.

Python Implementation for MCP Pipelines

Here is the exact code snippet to intercept tool calls, compute hashes, and force deterministic validation in your Python pipeline.


python
import hashlib
import json
import time
import requests

def generate_payload_hash(payload: dict) -> str:
    serialized = json.dumps(payload, sort_keys=True)
    return hashlib.sha256(serialized.encode("utf-8")).hexdigest()

def execute_verified_tool_call(endpoint: str, payload: dict) -> dict:
    request_hash = generate_payload_hash(payload)
    start_time = time.time()

    response = requests.post(endpoint, json=payload, timeout=10)

    if response.status_code != 200:
        raise RuntimeError(f"Tool call failed with status: {response.status_code}")

    response_data = response.json()
    response_hash = generate_payload_hash(response_data)

    audit_log = {
        "req_hash": request_hash,
        "res_hash": response_hash,
        "latency": time.time() - start_time,
        "verified": True
    }

    # Save audit_log to immutable storage here
    return response_data
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
agentsearchhq profile image
AgentSearch •

The empty-payload case is the one that bites hardest, and I'd add one check before the receipt: validate the response shape itself. A hash of an empty list is still a perfectly valid hash, so if the proxy swallows the call and hands back [] or {}, the receipt gets written and the audit passes. What fixed it for us was a small per-tool expectation (at least one row, required fields present, a non-empty body for anything that fetches a page) and returning a short status like ok, empty or blocked next to the data, so the agent can say "this didn't work" instead of reading silence as success.

Collapse
 
paul_irolla_6b5ae261d0224 profile image
Paul Irolla •

A success status only says the call returned. Whether the task was done is a check on the output against what was asked. Count the cost of the runs that pass that check, not the runs that returned.