How to Handle Error Responses in Claude Plugin Skills: A Complete Guide

Handle Claude plugin skill errors by inspecting both the immediate tool-level response and the downstream report status, using a three-step pattern: detect JSON-RPC errors, verify the report row exists, and poll until DONE or ERROR.

Claude plugin skills communicate with back-end services through the execute tool, typically targeting the tres-mcp GraphQL BFF. Because this backend returns errors across multiple channels, robust error handling requires checking every layer of the response. This guide explains the complete pattern, drawn from the anthropics/claude-plugins-community source code.

The Two-Layer Error Model

The tres-mcp backend signals failures through three distinct channels. Your skill must check all of them:

Error Channel Source Location Meaning
HTTP 400 with error or error_type fields run_report_matrix.py lines 147-148 Malformed request (wrong types, missing filters). No report created.
GraphQL errors array run_report_matrix.py line 165 Query rejected at GraphQL layer (syntax errors, unknown fields). No data returned.
Report status: ERROR run_report_matrix.py lines 340-341 Generator ran but failed to complete. Report exists but is unusable.

Missing any of these checks leads to silent failures or false positives.

Step 1: Detect Immediate Tool-Level Errors

The first checkpoint unwraps the raw tool result. In tres-finance-plugin/skills/tres-report-create/tests/run_report_matrix.py (lines 147-165), the skill inspects for both JSON-RPC errors and GraphQL-level errors:

def trigger_report(vars: dict) -> dict:
    result = execute_tool("mcp", "execute", vars)   # tool call

    
    if "error" in result:
        raise RuntimeError(f"Trigger error: {result['error']}")
    
    if "errors" in result:
        return {"errors": result["errors"]}          # GraphQL-level errors

    
    return result

Key distinction: The "error" field indicates a JSON-RPC layer failure, while "errors" contains GraphQL validation problems. Handle both separately.

Step 2: Verify the Report Row Exists (Silent-Failure Guard)

A dangerous edge case: the BFF may return HTTP 200 with no error field yet still fail to create a report row. According to SKILL.md §Step 1c (lines 108-130), you must query the report table by exact exportName to confirm creation:

def verify_row(export_name: str) -> dict:
    query = """
    query($name: String, $ordering: String, $limit: Int) {
      report(name: $name, ordering: $ordering, limit: $limit) {
        results { id name status }
      }
    }"""
    variables = {
        "name": export_name,
        "ordering": "-created_at",
        "limit": 1
    }
    resp = execute_tool("mcp", "query", {
        "query": query,
        "variables": variables
    })
    
    if not resp["data"]["report"]["results"]:
        raise RuntimeError("Silent failure: no report row created")
    
    return resp["data"]["report"]["results"][0]

If verification fails, retry once after fixing variable naming or types, then abort with a clear user message.

Step 3: Poll Until Final Status

Reports start with status IN_PROGRESS. Per SKILL.md §Step 2 (lines 56-62), poll until the report reaches terminal state:

def poll_report(export_name: str, max_attempts: int = 10):
    for attempt in range(max_attempts):
        row = verify_row(export_name)
        status = row["status"]
        
        if status == "DONE":
            return row["link"]
        
        if status == "ERROR":
            raise RuntimeError(
                f"Report error: {row.get('errorMessage', 'unknown')}"
            )
        
        time.sleep(60)   # wait before next poll

    
    raise TimeoutError("Report did not finish in time")

When status is ERROR, consult the error-to-fix mapping in SKILL.md lines 94-102 to adjust input parameters.

Critical Error Prevention Practices

The SKILL.md documentation (lines 103-112) specifies strict requirements that prevent common error conditions:

  • Strict variable typing — Declare variables with exact GraphQL types: String, ReportOutputFormat, DateTime, [String]. Type mismatches trigger HTTP 400 errors.

  • Exact naming — Use exportName, exportFormat, currency, outputFormat, timestamp_Gte, timestamp_Lte exactly. Inline literals or renamed variables cause silent failures.

  • Unique exportName per run — Ensures the verification query matches the newly created row (line 129).

  • Graceful fallback — When IN_PROGRESS exceeds retry limits, report timeout and suggest alternatives.

  • Markdown-formatted links — Present presigned download URLs as markdown rather than raw text (line 67).

Key Files for Error Handling Reference

File Purpose
tres-finance-plugin/skills/tres-report-create/tests/run_report_matrix.py Core error-unwrapping and polling logic (lines 147-165, 340-341)
tres-finance-plugin/skills/tres-report-create/SKILL.md Full workflow, error-to-fix table, variable requirements
tres-finance-plugin/skills/tres-report-analyzer/scripts/analyze_report.py Downstream analysis of successful downloads
.claude-plugin/plugin.json Skill entry-point and execute tool definition

Summary

  • Check two error channels: Inspect result["error"] for JSON-RPC failures and result["errors"] for GraphQL problems immediately after tool execution.

  • Prevent silent failures: Query the report table by exact exportName to confirm row creation before assuming success.

  • Poll to completion: Monitor status field until DONE (return link) or ERROR (surface message and apply fix).

  • Enforce strict contracts: Use exact variable names and GraphQL types to avoid HTTP 400 and silent failure modes.

Frequently Asked Questions

What causes HTTP 400 errors in Claude plugin skills?

HTTP 400 errors occur when request variables have incorrect types or missing required filters. The error or error_type fields in the tool result indicate these validation failures. Fix by matching exact GraphQL types specified in SKILL.md lines 103-112.

How do I detect silent failures where no report is created?

Query the report table using the exact exportName as shown in SKILL.md §Step 1c. If results is empty despite no tool-level error, a silent failure occurred. Retry once after correcting variable names or types.

What should I do when a report status remains IN_PROGRESS too long?

Implement a maximum attempt limit (recommended: 10 attempts with 60-second delays). If exceeded, raise TimeoutError and report to the user with alternative report suggestions. Never leave users waiting indefinitely.

How do I handle the ERROR status in a report row?

Extract the errorMessage field and surface it to the user. Then consult the error-to-fix mapping in SKILL.md lines 94-102 to determine which input parameters need adjustment before retrying.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →