Skip to content

Fix error message compliance in 5 pkg/workflow files - #52180

Merged
pelikhan merged 4 commits into
mainfrom
copilot/squad-plan-fix-error-messages-again
Aug 12, 2026
Merged

Fix error message compliance in 5 pkg/workflow files#52180
pelikhan merged 4 commits into
mainfrom
copilot/squad-plan-fix-error-messages-again

Conversation

Copilot AI commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

The errormessage linter flagged five pkg/workflow files as low-compliance (13–48%): error messages used negative wording (invalid, must, cannot, failed) without stating expected behavior or showing a fix. This rewrites the flagged messages per .github/skills/error-messages/SKILL.md ([what's wrong]. [what's expected]. [example]). No control-flow or validation-logic changes.

Changes

  • checkout_config_parser.go — each type/shape error now names the expected type and shows the YAML snippet. The one wrapping error was rephrased so the wrapped cause stays at the end of the message rather than mid-sentence.
  • safe_outputs_data_schema.go — schema errors state the expected shape and include a schema fragment (properties:, required: [...], additionalProperties: false).
  • stop_after.goon:, stop-after, and skip-if-* errors gained expected-format text and examples. Pre-existing Examples: headers were changed to Example:; the linter matches whole words, so the plural form was not counted as guidance.
  • model_identifier.go — ABNF grammar violations now describe the expected token shape with a concrete identifier example.
  • engine_driver_validation.go — converted all 14 fmt.Errorf calls to NewValidationError(field, value, reason, suggestion), as the linter requires for *_validation.go. Substrings asserted by existing tests (safe basename, unsupported extension, empty path segments, …) are preserved in reason.
  • error_message_quality_test.go — one new TestErrorMessageQuality case per touched file.

Example

// before
return nil, errors.New("checkout.lfs must be a boolean")

// after
return nil, errors.New("checkout.lfs must be a boolean. Example:\ncheckout:\n  lfs: true")
// engine_driver_validation.go, before
return fmt.Errorf("engine.driver has unsupported extension %q (found: %s). Must be a JavaScript file …", ext, name, constants.DocsEnginesURL)

// after
return NewValidationError(
    "engine.driver",
    name,
    fmt.Sprintf("engine.driver has unsupported extension %q", ext),
    fmt.Sprintf("Use a JavaScript file ending with .js, .cjs, or .mjs, or a bare command name without an extension.\n\nExample:\nengine:\n  driver: .github/drivers/driver.cjs\n\nSee: %s", constants.DocsEnginesURL),
)

The analyzer (/tmp/gh-aw-linters -errormessage.changed-files=…) reports no findings for these five files after the change.

Copilot AI and others added 2 commits August 11, 2026 23:37
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix error message compliance in multiple files Fix error message compliance in 5 pkg/workflow files Aug 11, 2026
Copilot AI requested a review from pelikhan August 11, 2026 23:40
@pelikhan
pelikhan marked this pull request as ready for review August 12, 2026 00:06
Copilot AI balanced review requested due to automatic review settings August 12, 2026 00:06
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • api.individual.githubcopilot.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.individual.githubcopilot.com"

See Network Configuration for more information.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Ponytail Reviewer completed successfully!

Lean already. Ship. Reviewed PR #52180 diff for over-engineering: all changes are mechanical error-message text additions (Example/Expected clauses) via the pre-existing NewValidationError helper. No new abstractions, dependencies, or dead flexibility introduced — nothing to cut.

Generated by Ponytail Reviewer for #52180

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

🧪 Test quality analysis by Test Quality Sentinel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (228 new lines in pkg/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/52180-structured-validation-error-messages.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-52180: Structured Validation Error Messages

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 52180-structured-validation-error-messages.md for PR #52180).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 81 AIC · ⌖ 21.4 AIC · ⊞ 8.7K ·
Comment /review to run again

@github-actions github-actions Bot mentioned this pull request Aug 12, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates workflow validation errors to be actionable and example-driven.

Changes:

  • Adds expected formats and YAML examples to validation errors.
  • Converts engine-driver errors to structured NewValidationError values.
  • Adds representative error-quality tests.
Show a summary per file
File Description
pkg/workflow/checkout_config_parser.go Improves checkout errors.
pkg/workflow/safe_outputs_data_schema.go Clarifies schema errors.
pkg/workflow/stop_after.go Adds trigger configuration examples.
pkg/workflow/model_identifier.go Expands identifier syntax guidance.
pkg/workflow/engine_driver_validation.go Introduces structured validation errors.
pkg/workflow/error_message_quality_test.go Tests representative messages.

Review details

Tip

Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Suppressed comments (2)

pkg/workflow/stop_after.go:358

  • The singular Example: combines both accepted forms into one YAML document with duplicate skip-if-no-match keys. That document is rejected or interpreted ambiguously rather than demonstrating correct usage. Keep one valid example.
				return nil, fmt.Errorf("skip-if-no-match value must be a string or object, got %T. Example:\n  skip-if-no-match: \"is:pr is:open\"\n  skip-if-no-match:\n    query: \"is:pr is:open\"\n    min: 3", skipIfNoMatch)

pkg/workflow/stop_after.go:495

  • This Example: contains three duplicate skip-if-check-failing keys, including a null-valued one, so it is not a single valid corrective example. Show one accepted object form instead.
				return nil, fmt.Errorf("skip-if-check-failing value must be true or an object, got %T. Example:\n  skip-if-check-failing:\n  skip-if-check-failing: true\n  skip-if-check-failing:\n    include:\n      - build\n    branch: main\n    allow-pending: true", skipIfCheckFailing)
  • Files reviewed: 7/7 changed files
  • Comments generated: 6
  • Review effort level: Balanced

cfg, err := checkoutConfigFromMap(singleMap)
if err != nil {
return nil, fmt.Errorf("invalid checkout configuration: %w", err)
return nil, fmt.Errorf("invalid checkout configuration, expected an object with checkout fields such as repository, ref, or path: %w", err)
}, nil
default:
return nil, fmt.Errorf("skip-if-match value must be a string or object, got %T. Examples:\n skip-if-match: \"is:issue is:open\"\n skip-if-match:\n query: \"is:pr is:open\"\n max: 3", skipIfMatch)
return nil, fmt.Errorf("skip-if-match value must be a string or object, got %T. Example:\n skip-if-match: \"is:issue is:open\"\n skip-if-match:\n query: \"is:pr is:open\"\n max: 3", skipIfMatch)
return fmt.Errorf("model identifier: character %q is not allowed in model token %q (segment type: model)", r, token)
}
return fmt.Errorf("model identifier: model token %q is syntactically invalid (segment type: model)", token)
return fmt.Errorf("model identifier: model token %q is syntactically invalid (segment type: model). Expected letters, digits, '-', '_', or '.'. Example: model: openai/gpt-4o", token)
return fmt.Errorf("model identifier: character %q is not allowed in bare name %q (segment type: alias)", r, name)
}
return fmt.Errorf("model identifier: bare name %q is syntactically invalid (segment type: alias)", name)
return fmt.Errorf("model identifier: bare name %q is syntactically invalid (segment type: alias). Expected letters, digits, '-', '_', or '.'. Example: model: gpt-4o", name)
Comment on lines +220 to +223
"engine.driver",
workflowData.EngineConfig.ID,
"inline engine.driver sources are only supported for the copilot engine",
fmt.Sprintf("Set engine.id to copilot, or use a driver file path instead of inline source.\n\nExample:\nengine:\n id: copilot\n driver:\n node: |\n console.log(\"hello\")\n\nSee: %s", constants.DocsEnginesURL),
"engine.driver."+inlineDriver.Runtime,
"",
fmt.Sprintf("engine.driver.%s must not be empty", inlineDriver.Runtime),
fmt.Sprintf("Provide the inline driver source.\n\nExample:\nengine:\n driver:\n %s: |\n console.log(\"hello\")\n\nSee: %s", inlineDriver.Runtime, constants.DocsEnginesURL),

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Error Message Compliance

This PR systematically improves error messages across 5 pkg/workflow files, adding context and Example: blocks following the project's error-message style guide. Overall the changes are correct and well-structured.

What's good

  • NewValidationError is used correctly in engine_driver_validation.go, with separate field, reason, and suggestion parameters.
  • Examples:Example: normalisation is applied consistently throughout stop_after.go.
  • New test cases in error_message_quality_test.go verify that each improved message contains the expected substrings.
  • Model identifier, safe-outputs data schema, and stop-after errors all now include actionable examples.

One issue

See inline comment on checkout_config_parser.go line 124: the checkout.token (deprecated field) error message example uses github-token without explaining why, mixing type-error guidance with deprecation migration guidance in a confusing way.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • proxy.golang.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "proxy.golang.org"

See Network Configuration for more information.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 76.8 AIC · ⌖ 6.39 AIC · ⊞ 5.4K

s, ok := v.(string)
if !ok {
return nil, errors.New("checkout.token must be a string")
return nil, errors.New("checkout.token must be a string. Example:\ncheckout:\n github-token: ${{ secrets.MY_TOKEN }}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error for the deprecated token key says checkout.token must be a string but the example redirects to github-token without explaining why:

return nil, errors.New("checkout.token must be a string. Example:\ncheckout:\n  github-token: ${{ secrets.MY_TOKEN }}")

A user who passes token: 123 (wrong type) sees an example using a different field name, conflating type guidance with deprecation migration guidance. This can be confusing — they may think the fix is to rename the key, not to change the value type.

Suggested improvement:

return nil, errors.New("checkout.token must be a string (\"token\" is deprecated; prefer \"github-token\"). Example:\ncheckout:\n  github-token: ${{ secrets.MY_TOKEN }}")

This makes the deprecation intent explicit so the example makes sense in context.

@copilot please address this.

@github-actions

Copy link
Copy Markdown
Contributor

Test Quality Sentinel Report — PR #52180

PR Overview

  • Title: Fix error message compliance in 5 pkg/workflow files
  • Repository: github/gh-aw
  • Actor: pelikhan

Test Files Modified (1 file)

  • pkg/workflow/error_message_quality_test.go — modified (2 commits)

Test Modifications Breakdown

Commit 162a3ba ("Improve error message compliance in 5 workflow files"):

  • Added 5 new test cases (86 lines) to TestErrorMessageQuality (table-driven)

Commit 0d15c20 ("Fix expected error path in data schema quality test"):

  • Updated 1 assertion in the "safe-outputs data schema error" test case
  • Fixed expected error message path validation

New Test Cases Added (5 total)

Test Case Classification
Test Case Design Invariant Value Type
checkout field type error error message quality contract high_value design_test
safe-outputs data schema error validation error completeness high_value design_test
model identifier error parser error messaging high_value design_test
skip-if-match query type error frontmatter validation guidance high_value design_test
engine.driver extension error config validation with suggestions high_value design_test

Assertion Quality Analysis

Framework: Go with testify/assert and testify/require

Pattern Per Test:

  • 1 require.Error() guard
  • 3–5 assert.Contains() assertions (validates error message content)
  • 1 assert.Greater() assertion (validates message length > 30 chars)
  • Conditional t.Errorf() (vagueness detection)

Red Flag Check:

  • ✅ No forbidden mocking libraries (gomock, testify/mock, .EXPECT())
  • ✅ All assertions include descriptive failure messages
  • ✅ Build tag present: (go/redacted):build !integration
  • ✅ All tests verify user-facing error messages (design contract)

Test vs Production Ratio

  • Test file: +86 lines in commit 162a3ba
  • Production files: +228 lines across 5 files
  • Ratio: 86:228 ≈ 0.38:1 (threshold: ≤2:1) ✅

Design Contracts Verified

  1. Error messages include "Example:" for corrective guidance
  2. Error messages are specific (length > 30 chars, no vague phrases)
  3. Type errors show actual type received
  4. Validation errors reference full path (e.g., "safe-outputs.data.additionalProperties")
  5. Complex errors include "Suggestion:" or "Choose one:" for multi-option guidance

Quality Score

Test Quality Score: 100/100 — Excellent

Calculation

design_tests = 5/5 (100% verify design contracts)
edge_case_coverage = 5/5 (100% cover error scenarios)
duplicate_patterns = 0 (no duplication)
inflation = 0 (0.38:1 << 2:1 threshold)
implementation_tests = 0% (all behavioral, not impl-detail)

score = 40 + 30 + 20 + 10 = 100

Pass Criteria

  • ✅ Implementation ratio 0% (threshold: ≤30%)
  • ✅ No coding guideline violations
  • ✅ No forbidden mocking
  • ✅ Build tags present
  • ✅ Well-balanced test:production ratio

Conclusion

APPROVE — Exemplary test quality

This PR demonstrates excellent testing practices:

  • All 5 new tests verify error message design contracts
  • Comprehensive assertion patterns with descriptive context
  • 100% edge-case / error-scenario coverage
  • Zero implementation-detail testing
  • Properly balanced coverage (0.38:1 test:production ratio)
  • Tests correctly validate real product behavior (evidenced by follow-up fix in commit 0d15c20)

Test Quality Sentinel verdict: Ready to merge ✅

🧪 Test quality analysis by Test Quality Sentinel · haiku45 · 23.9 AIC · ⌖ 4.23 AIC · ⊞ 7.6K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 100/100 — Excellent

Implementation tests: 0% (threshold: ≤30%)

All 5 new test cases verify error message design contracts with comprehensive assertions and edge-case coverage. No violations detected.

🎯 Ready to merge!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /codebase-design and /tdd — requesting changes on three targeted issues.

📋 Key Themes & Highlights

Key Themes

  • Example field mismatch: the checkout.token backward-compat error shows a github-token example, which misdirects users to the wrong field.
  • Lost diagnostic context: two NewValidationError calls in engine_driver_validation.go pass value: "", silently suppressing the Value: section users rely on for triage.
  • Inconsistent example format: model_identifier.go appends . Example: model: openai/gpt-4o (space-separated, same-line) while all other changed files use a newline-separated YAML block.
  • Missing test case: the skip-if-no-match error messages updated in stop_after.go have no corresponding TestErrorMessageQuality case.

Positive Highlights

  • ✅ Systematic and focused change — no control-flow or logic touches
  • engine_driver_validation.go correctly migrated to NewValidationError() per the *_validation.go convention
  • ✅ 80 lines of new test coverage exercise the new message format across all five files

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 112.8 AIC · ⌖ 6.91 AIC · ⊞ 7K
Comment /matt to run again

s, ok := v.(string)
if !ok {
return nil, errors.New("checkout.token must be a string")
return nil, errors.New("checkout.token must be a string. Example:\ncheckout:\n github-token: ${{ secrets.MY_TOKEN }}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] The checkout.token type error shows a github-token example — a user who wrote token: 123 sees a fix pointing to the new field name instead of the field they actually used.

💡 Suggested fix

Show the deprecated field in the example, with a note to prefer the new field:

return nil, errors.New("checkout.token must be a string. Example:
checkout:
  token: \${{ secrets.MY_TOKEN }}
# Prefer checkout.github-token in new workflows")

@copilot please address this.

return fmt.Errorf("engine.driver: exactly one runtime key is allowed (node, python, go, java); found multiple.\n\nSee: %s", constants.DocsEnginesURL)
return NewValidationError(
"engine.driver",
"",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] value: "" passed to NewValidationError for the MultipleRuntime case drops diagnostic context — the rendered error will silently skip the Value: section, giving less signal than the old fmt.Errorf message.

💡 Suggested fix

Provide a meaningful value string so the user sees which field triggered the error. For the multiple-runtime case you could stringify the keys found:

return NewValidationError(
    "engine.driver",
    "multiple runtime keys",
    "engine.driver accepts exactly one runtime key (node, python, go, java); found multiple",
    ...
)

Same pattern applies to the empty-source case at line ~230: pass inlineDriver.Runtime as value so the field name in context is visible.

@copilot please address this.

modelIdentifierLog.Printf("Parsing model identifier: %q", s)
if s == "" {
return nil, errors.New("model identifier must not be empty")
return nil, errors.New("model identifier must not be empty. Expected a bare alias, or a provider-scoped name. Example: model: openai/gpt-4o")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] The Example: guidance is appended inline as . Example: model: openai/gpt-4o (space-separated, same line) while every other changed file uses the pattern . Example: key: field: value (newline-separated YAML block). The inconsistency makes the output less readable in terminals, which render multi-line errors with indented context.

💡 Suggested fix

Use a newline-prefixed YAML block for consistency:

return nil, errors.New("model identifier must not be empty. Expected a bare alias, or a provider-scoped name.
Example:
  model: openai/gpt-4o")

The same pattern should be applied to all the other appended examples in this file (lines ~233, ~240, ~245, ~253, ...).

@copilot please address this.

},
shouldNotBeVague: true,
},
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] A test case was added for skip-if-match but the parallel skip-if-no-match error messages updated in the same diff (lines ~722–732 of stop_after.go) have no coverage here — only the skip-if-match path is exercised.

💡 Suggested addition

Add a sibling test case:

{
    name: "skip-if-no-match query type error includes example",
    testFunc: func() error {
        c := NewCompiler()
        frontmatter := map[string]any{
            "on": map[string]any{
                "skip-if-no-match": map[string]any{
                    "query": 123,
                },
            },
        }
        _, err := c.extractSkipIfNoMatchFromOn(frontmatter)
        return err
    },
    shouldContain: []string{
        "skip-if-no-match 'query' field must be a string",
        "Example:",
        "query:",
    },
    shouldNotBeVague: true,
},

@copilot please address this.

@pelikhan
pelikhan merged commit 2f8d913 into main Aug 12, 2026
@pelikhan
pelikhan deleted the copilot/squad-plan-fix-error-messages-again branch August 12, 2026 00:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants