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_Lteexactly. Inline literals or renamed variables cause silent failures. -
Unique
exportNameper run — Ensures the verification query matches the newly created row (line 129). -
Graceful fallback — When
IN_PROGRESSexceeds 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 andresult["errors"]for GraphQL problems immediately after tool execution. -
Prevent silent failures: Query the
reporttable by exactexportNameto confirm row creation before assuming success. -
Poll to completion: Monitor
statusfield untilDONE(return link) orERROR(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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →