Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Systematically review workflow traces to identify failure modes before building evaluators. Use when starting an eval project, after significant pipeline changes, or when production quality drops.
.claude/skills/growthxai-output-eval-error-analysis/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 105% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 258% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 78% | 0% |
| case-04 | ✗→✓ | ▲ Improved | -52% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 53% | 0% |
Review real workflow traces and categorize how your workflow fails before writing any evaluators. Evaluators built without error analysis target generic qualities ("is this good?") instead of the specific ways your workflow actually breaks. This skill walks you through the process.
Gather 50-100 representative workflow executions. More traces = more reliable failure categories.
List recent workflow executions and pull their traces:
bash# List recent runs for a workflow npx output workflow runs list <workflowName> # Pull a specific trace as JSON npx output workflow debug <workflowId> --json
Download production traces directly into dataset YAML files:
bash# Download up to 20 recent traces as dataset files npx output workflow dataset generate <workflowName> --download --limit 20
This creates YAML files in tests/datasets/ with the input and last_output fields populated from real executions.
If production traces are sparse, generate traces from scenario inputs:
bash# Generate a dataset from a scenario file npx output workflow dataset generate <workflowName> basic --name basic_trace # Generate from inline JSON npx output workflow dataset generate <workflowName> --input '{"topic": "AI safety"}' --name ai_safety_trace
Run enough inputs to get 50+ traces. Prioritize diversity over volume — vary inputs across the dimensions you expect to matter.
Review each trace one at a time. For each trace, record:
| Field | What to write | |-------|---------------| | Trace ID | The workflow execution ID | | Verdict | Pass or Fail (binary — no "partial" at this stage) | | Root cause | If Fail: what specifically went wrong and why | | Notes | Anything surprising or worth remembering |
Create a file to track your reviews. A simple markdown table works:
markdown# Error Analysis: <workflow_name> # Date: YYYY-MM-DD # Traces reviewed: 0 / 50 | # | Trace ID | Verdict | Root Cause | Notes | |---|----------|---------|------------|-------| | 1 | abc-123 | Fail | Hallucinated a URL that doesn't exist | Common with technical topics | | 2 | def-456 | Pass | — | Clean output | | 3 | ghi-789 | Fail | Ignored the "formal tone" requirement | Input had conflicting signals |
Open the JSON trace and examine:
Review at least 30 traces before naming any failure categories. Premature categorization causes you to see patterns that aren't there and miss patterns that are. Just record what you observe.
After reviewing 30+ traces, patterns will emerge. Group your failures into 5-10 categories based on root cause, not surface symptoms.
For a blog generation workflow after reviewing 60 traces:
| Category | Count | Rate | Example | |----------|-------|------|---------| | Hallucinated URLs | 8 | 13% | Invented links to non-existent pages | | Tone mismatch | 6 | 10% | Casual tone when formal was requested | | Off-topic drift | 5 | 8% | Blog about "AI" drifted to unrelated ML history | | Missing sections | 4 | 7% | Skipped "conclusion" when explicitly requested | | Too short | 3 | 5% | Under 200 words when 500+ requested | | Total failures | 26 | 43% | | | Passes | 34 | 57% | |
Add ground_truth labels to your dataset YAML files so evaluators can validate against them. Each failure category maps to a future evaluator name.
yamlname: ai_safety_trace input: topic: "AI safety" tone: "formal" min_length: 500 last_output: output: title: "Understanding AI Safety" blog_post: "AI safety is super important and stuff..." executionTimeMs: 3200 date: '2026-03-25T00:00:00.000Z' ground_truth: # Global ground truth (available to all evaluators) human_verdict: fail failure_categories: - tone_mismatch notes: "Used casual language despite formal tone request" # Per-evaluator ground truth evals: check_tone: expected_tone: formal verdict: fail check_length: min_length: 500 verdict: pass check_hallucinated_urls: verdict: pass
The ground_truth.evals.<evaluator_name> fields map directly to the evaluator names you'll use in verify(). Each evaluator receives its own ground truth merged with the top-level ground truth via context.ground_truth.
You don't need to label every dataset for every category. Focus on:
human_verdict (pass/fail)Not every failure category needs an evaluator. Use this decision tree:
Is this failure caused by a fixable prompt/tool gap?
├─ YES → Fix the prompt or add the missing tool first
│ Re-run error analysis after the fix
└─ NO → Will this failure recur and need ongoing monitoring?
├─ YES → Build an evaluator
│ Can it be checked with deterministic code?
│ ├─ YES → Use Verdict.* helpers (contains, matches, gte, etc.)
│ └─ NO → Use judgeVerdict() with an LLM judge prompt
└─ NO → Document it and move on (rare edge case)Build evaluators for the highest-rate failure categories first. A failure at 13% matters more than one at 2%.
Many failures that seem subjective have objective proxies:
| Failure | Seems like... | But you can check with... | |---------|---------------|---------------------------| | "Too short" | Subjective | Verdict.gte(output.length, threshold) | | "Missing section" | Needs LLM | Verdict.contains(output, "## Conclusion") | | "Hallucinated URLs" | Needs LLM | Extract URLs with regex, verify with HTTP HEAD | | "Wrong format" | Needs LLM | Verdict.matches(output, expectedPattern) |
Reserve LLM judges for genuinely subjective criteria: tone, relevance, faithfulness, coherence.
Create a mapping document that connects your failure categories to planned evaluators:
markdown# Evaluator Plan: blog_generator | Category | Rate | Evaluator Type | Evaluator Name | Criticality | |----------|------|----------------|----------------|-------------| | Hallucinated URLs | 13% | Code (URL extraction + HTTP check) | check_urls | required | | Tone mismatch | 10% | LLM judge | check_tone | required | | Off-topic drift | 8% | LLM judge | check_topic | required | | Missing sections | 7% | Code (string contains) | check_sections | required | | Too short | 5% | Code (length check) | check_length | informational |
This becomes your implementation roadmap. Use criticality: 'required' for failure categories that should block a passing verdict. Use 'informational' for nice-to-have checks.
output-dev-eval-testing to implement each evaluator with verify() and wire them into evalWorkflow()output-eval-judge-prompt to write effective .prompt filesoutput-eval-dataset-design to generate diverse test casesVerdict.contains() worksoutput-dev-eval-testing — Implement evaluators with verify(), Verdict, and evalWorkflow()output-eval-judge-prompt — Design LLM judge prompts for subjective failure modesoutput-eval-dataset-design — Generate diverse datasets when real traces are sparseoutput-eval-validate-judge — Validate LLM judges against human labelsoutput-eval-audit — Audit an existing eval suite for trustworthinessoutput-workflow-trace — Retrieve and analyze workflow execution traces| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 12,753 | 11,690 | -8% | 1 | 1 | 0% | 2,171 | 4,451 | +105% | 0 | 0 | — |
case-02 | fail→pass | 30,852 | 8,811 | -71% | 1 | 1 | 0% | 1,232 | 4,410 | +258% | 0 | 0 | — |
case-03 | fail→pass | 13,409 | 8,184 | -39% | 1 | 1 | 0% | 2,203 | 3,911 | +78% | 0 | 0 | — |
case-04 | fail→pass | 35,227 | 2,425 | -93% | 1 | 1 | 0% | 6,187 | 2,986 | -52% | 0 | 0 | — |
case-05 | fail→pass | 11,460 | 2,546 | -78% | 1 | 1 | 0% | 1,951 | 2,985 | +53% | 0 | 0 | — |
case-06 | fail→pass | 10,442 | 2,584 | -75% | 1 | 1 | 0% | 1,871 | 3,024 | +62% | 0 | 0 | — |
case-13 | fail→pass | 12,816 | 6,123 | -52% | 1 | 1 | 0% | 1,944 | 3,613 | +86% | 0 | 0 | — |
case-07 | fail→pass | 15,471 | 5,296 | -66% | 1 | 1 | 0% | 2,508 | 3,374 | +35% | 0 | 0 | — |
case-08 | fail→pass | 11,913 | 5,927 | -50% | 1 | 1 | 0% | 1,775 | 3,426 | +93% | 0 | 0 | — |
case-09 | pass→pass | 15,968 | 7,311 | -54% | 1 | 1 | 0% | 2,291 | 3,779 | +65% | 0 | 0 | — |
case-10 | pass→pass | 12,858 | 6,013 | -53% | 1 | 1 | 0% | 1,825 | 3,468 | +90% | 0 | 0 | — |
case-11 | fail→fail | 10,045 | 6,213 | -38% | 1 | 1 | 0% | 1,472 | 3,574 | +143% | 0 | 0 | — |
case-12 | fail→pass | 13,770 | 6,907 | -50% | 1 | 1 | 0% | 1,976 | 3,616 | +83% | 0 | 0 | — |
case-14 | pass→pass | 8,791 | 5,685 | -35% | 1 | 1 | 0% | 1,357 | 3,440 | +154% | 0 | 0 | — |
case-15 | pass→pass | 15,687 | 8,009 | -49% | 1 | 1 | 0% | 2,397 | 3,919 | +63% | 0 | 0 | — |
case-16 | pass→pass | 9,566 | 4,711 | -51% | 1 | 1 | 0% | 1,486 | 3,388 | +128% | 0 | 0 | — |
case-17 | pass→pass | 19,211 | 4,665 | -76% | 1 | 1 | 0% | 2,644 | 3,287 | +24% | 0 | 0 | — |
case-18 | fail→pass | 11,231 | 6,440 | -43% | 1 | 1 | 0% | 1,676 | 3,627 | +116% | 0 | 0 | — |
case-19 | fail→fail | 10,896 | 7,648 | -30% | 1 | 1 | 0% | 1,696 | 3,847 | +127% | 0 | 0 | — |
case-20 | pass→pass | 13,935 | 13,311 | -4% | 1 | 1 | 0% | 2,414 | 5,147 | +113% | 0 | 0 | — |
case-21 | pass→pass | 25,067 | 12,031 | -52% | 1 | 1 | 0% | 1,966 | 4,493 | +129% | 0 | 0 | — |
case-22 | pass→pass | 14,569 | 15,851 | +9% | 1 | 1 | 0% | 2,578 | 5,421 | +110% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +50 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.