Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Write consistent, actionable validation error messages in gh-aw.
.claude/skills/github-error-messages/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 221% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 161% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 394% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 117% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 116% | 0% |
Use this format for gh-aw validation errors. Keep messages clear, actionable, and example-driven.
[what's wrong]. [what's expected]. [example of correct usage]Make each error message answer three questions:
Avoid standalone negative wording. Pair it with expected behavior and a concrete fix.
| Avoid only-negative wording | Prefer constructive wording | |---|---| | invalid | expected + valid format/options | | cannot | requires + precondition | | must | should + example | | failed | action context + recovery step |
❌ invalid repo format: %s ✅ invalid repo format '%s' — expected 'owner/repo' format (for example: 'github/gh-aw')
❌ not in a git repository ✅ not in a git repository — run 'git init' or 'cd' to a git repository
NewValidationError vs fmt.ErrorfNewValidationError(field, value, reason, suggestion) in *_validation.go logic.field for the exact config pathreason for what failedsuggestion for an actionable fix with an examplefmt.Errorf for operational/wrapping errors (%w) where you are propagating a lower-level failure with context.fmt.Errorf("failed to X: %w", err) unless you add recovery guidance.Every suggestion should:
Example:
textUse one supported engine. ✓ Example: engine: copilot ✗ Avoid: engine: unknown
These examples follow the template and provide actionable guidance:
goreturn nil, fmt.Errorf("invalid time delta format: +%s. Expected format like +25h, +3d, +1w, +1mo, +1d12h30m", deltaStr)
✅ Why it's good:
goreturn "", fmt.Errorf("manual-approval value must be a string, got %T. Example: manual-approval: \"production\"", val)
✅ Why it's good:
goreturn fmt.Errorf("invalid engine: %s. Valid engines are: copilot, claude, codex, custom. Example: engine: copilot", engineID)
✅ Why it's good:
goreturn fmt.Errorf("tool '%s' mcp configuration must specify either 'command' or 'container'. Example:\ntools:\n %s:\n command: \"npx @my/tool\"", toolName, toolName)
✅ Why it's good:
These examples lack clarity or actionable guidance:
goreturn fmt.Errorf("invalid format")
❌ Problems:
goreturn fmt.Errorf("manual-approval value must be a string")
❌ Problems:
goreturn fmt.Errorf("invalid engine: %s", engineID)
❌ Problems:
Always include examples for:
go fmt.Errorf("invalid date format. Expected: YYYY-MM-DD HH:MM:SS. Example: 2024-01-15 14:30:00")
go fmt.Errorf("invalid permission level: %s. Valid levels: read, write, none. Example: permissions:\n contents: read", level)
go fmt.Errorf("timeout-minutes must be an integer, got %T. Example: timeout-minutes: 10", value)
go fmt.Errorf("invalid MCP server config. Example:\nmcp-servers:\n my-server:\n command: \"node\"\n args: [\"server.js\"]")
Examples can be omitted when:
go return fmt.Errorf("failed to parse configuration: %w", err)
go return fmt.Errorf("duplicate unit '%s' in time delta: +%s", unit, deltaStr)
go return fmt.Errorf("unsupported feature. See https://docs.example.com/features")
%s - strings%d - integers %T - type of value%v - general value%w - wrapped errorsFor YAML configuration examples spanning multiple lines:
gofmt.Errorf("invalid config. Example:\ntools:\n github:\n mode: \"remote\"")
Use proper YAML syntax in examples:
go// Good - shows quotes when needed fmt.Errorf("Example: name: \"my-workflow\"") // Good - shows no quotes for simple values fmt.Errorf("Example: timeout-minutes: 10")
Use the same field names as in YAML:
go// Good - matches YAML field name fmt.Errorf("timeout-minutes must be positive") // Bad - uses different name fmt.Errorf("timeout must be positive")
All improved error messages should have corresponding tests:
gofunc TestErrorMessageQuality(t *testing.T) { err := validateSomething(invalidInput) require.Error(t, err) // Error should explain what's wrong assert.Contains(t, err.Error(), "invalid") // Error should include expected format or values assert.Contains(t, err.Error(), "Expected") // Error should include example assert.Contains(t, err.Error(), "Example:") }
When improving existing error messages:
go// Time deltas fmt.Errorf("invalid time delta format: +%s. Expected format like +25h, +3d, +1w, +1mo, +1d12h30m", input) // Dates fmt.Errorf("invalid date format: %s. Expected: YYYY-MM-DD or relative like -1w. Example: 2024-01-15 or -7d", input) // URLs fmt.Errorf("invalid URL format: %s. Expected: https:// URL. Example: https://api.example.com", input)
go// Boolean expected fmt.Errorf("read-only must be a boolean, got %T. Example: read-only: true", value) // String expected fmt.Errorf("workflow name must be a string, got %T. Example: name: \"my-workflow\"", value) // Object expected fmt.Errorf("permissions must be an object, got %T. Example: permissions:\n contents: read", value)
go// Engine selection fmt.Errorf("invalid engine: %s. Valid engines: copilot, claude, codex, custom. Example: engine: copilot", id) // Permission levels fmt.Errorf("invalid permission level: %s. Valid levels: read, write, none. Example: contents: read", level) // Tool modes fmt.Errorf("invalid mode: %s. Valid modes: local, remote. Example: mode: \"remote\"", mode)
go// Missing required field fmt.Errorf("tool '%s' missing required 'command' field. Example:\ntools:\n %s:\n command: \"node server.js\"", name, name) // Mutually exclusive fields fmt.Errorf("cannot specify both 'command' and 'container'. Choose one. Example: command: \"node server.js\"") // Invalid combination fmt.Errorf("http MCP servers cannot use 'container' field. Example:\ntools:\n my-http:\n type: http\n url: \"https://api.example.com\"")
pkg/workflow/time_delta.gopkg/workflow/*_test.goWhen writing error messages, consider:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-11 | pass→pass | 6,985 | 6,002 | -14% | 1 | 1 | 0% | 1,166 | 3,566 | +206% | 0 | 0 | — |
case-01 | fail→pass | 6,627 | 4,896 | -26% | 1 | 1 | 0% | 1,080 | 3,464 | +221% | 0 | 0 | — |
case-02 | fail→pass | 8,562 | 7,770 | -9% | 1 | 1 | 0% | 1,587 | 4,148 | +161% | 0 | 0 | — |
case-03 | fail→pass | 4,885 | 7,785 | +59% | 1 | 1 | 0% | 787 | 3,888 | +394% | 0 | 0 | — |
case-04 | fail→pass | 8,554 | 4,317 | -50% | 1 | 1 | 0% | 1,486 | 3,228 | +117% | 0 | 0 | — |
case-05 | fail→pass | 8,620 | 5,282 | -39% | 1 | 1 | 0% | 1,538 | 3,320 | +116% | 0 | 0 | — |
case-06 | fail→pass | 9,274 | 50,179 | +441% | 1 | 1 | 0% | 1,545 | 3,155 | +104% | 0 | 0 | — |
case-07 | fail→pass | 9,554 | 8,149 | -15% | 1 | 1 | 0% | 1,813 | 4,162 | +130% | 0 | 0 | — |
case-08 | fail→pass | 5,513 | 4,802 | -13% | 1 | 1 | 0% | 968 | 3,250 | +236% | 0 | 0 | — |
case-09 | pass→pass | 8,814 | 4,828 | -45% | 1 | 1 | 0% | 1,458 | 3,354 | +130% | 0 | 0 | — |
case-10 | pass→pass | 10,700 | 6,717 | -37% | 1 | 1 | 0% | 1,679 | 3,639 | +117% | 0 | 0 | — |
case-12 | fail→pass | 7,793 | 5,643 | -28% | 1 | 1 | 0% | 1,193 | 3,479 | +192% | 0 | 0 | — |
case-13 | fail→pass | 9,551 | 6,720 | -30% | 1 | 1 | 0% | 1,593 | 3,532 | +122% | 0 | 0 | — |
case-14 | fail→pass | 12,090 | 16,438 | +36% | 1 | 1 | 0% | 1,911 | 3,693 | +93% | 0 | 0 | — |
case-15 | fail→pass | 11,615 | 9,082 | -22% | 1 | 1 | 0% | 2,014 | 4,229 | +110% | 0 | 0 | — |
case-16 | fail→pass | 8,508 | 5,399 | -37% | 1 | 1 | 0% | 1,662 | 3,357 | +102% | 0 | 0 | — |
case-17 | fail→pass | 10,757 | 10,298 | -4% | 1 | 1 | 0% | 1,903 | 4,531 | +138% | 0 | 0 | — |
case-18 | pass→pass | 9,971 | 5,575 | -44% | 1 | 1 | 0% | 1,446 | 3,435 | +138% | 0 | 0 | — |
case-19 | pass→pass | 4,015 | 5,048 | +26% | 1 | 1 | 0% | 594 | 3,418 | +475% | 0 | 0 | — |
case-20 | pass→pass | 5,459 | 5,003 | -8% | 1 | 1 | 0% | 1,001 | 3,351 | +235% | 0 | 0 | — |
case-21 | pass→pass | 7,357 | 4,573 | -38% | 1 | 1 | 0% | 1,235 | 3,316 | +169% | 0 | 0 | — |
case-22 | pass→pass | 50,179 | 6,030 | -88% | 1 | 1 | 0% | 1,410 | 3,404 | +141% | 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. The headline lift of +64 percentage points is the difference between those two pass rates over the 22 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.