Skip to content

Enforce minimum create_issue body length in safe outputs schema and validator#38114

Merged
pelikhan merged 6 commits into
mainfrom
copilot/daily-report-documentation-quality
Jun 9, 2026
Merged

Enforce minimum create_issue body length in safe outputs schema and validator#38114
pelikhan merged 6 commits into
mainfrom
copilot/daily-report-documentation-quality

Conversation

Copilot AI commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

The daily documentation report issue thread identified that low-signal/placeholder issue bodies were getting through safe outputs, which contributed to empty or non-actionable generated content. This change enforces a stricter floor for create_issue body content by requiring at least 20 characters.

  • Validation source of truth

    • Added MinIssueBodyLength = 20 in pkg/workflow/safe_outputs_validation_config.go.
    • Applied MinLength to create_issue.body in the safe-output validation config map.
    • Added parity coverage in pkg/workflow/safe_output_validation_config_test.go to assert create_issue.body.MinLength == MinIssueBodyLength.
  • Tool schema alignment (runtime + compiler copies)

    • Added "minLength": 20 to create_issue.inputSchema.properties.body in:
      • actions/setup/js/safe_outputs_tools.json
      • pkg/workflow/js/safe_outputs_tools.json
    • Keeps MCP/runtime schema and embedded compiler schema behavior consistent.
  • Runtime-expression sample validation compatibility

    • Updated pkg/workflow/samples_validation.go so schema-aware placeholder substitution for string runtime expressions honors JSON Schema minLength constraints.
    • Prevents compile-time sample validation from failing when ${{ ... }} expressions are used in create_issue.body samples under the new minLength rule.
  • Targeted test updates

    • Extended safe_output_type_validator.test.cjs with explicit coverage for:
      • reject < 20 characters
      • reject exactly 19 characters (boundary - 1)
      • reject whitespace-only bodies after trimming
      • accept > 20 characters
      • accept exactly 20 characters (boundary)
    • Updated coupled sample tests in pkg/workflow/samples_validation_test.go so missing-required-field scenarios still validate the intended error path under the new constraint.
    • Added placeholder coverage to verify minLength-aware runtime-expression substitution behavior.
  • Scope cleanup

    • Removed unrelated workflow lockfile churn so this PR remains focused on create_issue body min-length enforcement and its directly coupled validation/test behavior.
const (
    MinIssueBodyLength = 20
)

"create_issue": {
    Fields: map[string]FieldValidation{
        "body": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength, MinLength: MinIssueBodyLength},
    },
}

Copilot AI and others added 3 commits June 9, 2026 13:00
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
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] Update documentation for quality report workflow Enforce minimum create_issue body length in safe outputs schema and validator Jun 9, 2026
Copilot AI requested a review from pelikhan June 9, 2026 13:17
@pelikhan pelikhan marked this pull request as ready for review June 9, 2026 13:30
Copilot AI review requested due to automatic review settings June 9, 2026 13:30

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

This PR tightens safe-output validation for create_issue by enforcing a minimum body length (20 chars) across the Go validation config, the tool JSON schemas used at runtime/compile time, and the JS validator tests to reduce low-signal/placeholder issue bodies.

Changes:

  • Added MinIssueBodyLength = 20 and enforced it via MinLength on create_issue.body in the Go safe-outputs validation config.
  • Updated both compiler-embedded and runtime tool schemas to include minLength: 20 for create_issue.body.
  • Updated JS validator tests and Go sample fixtures to satisfy the new constraint; workflow lockfile was also regenerated with many unrelated changes.
Show a summary per file
File Description
pkg/workflow/safe_outputs_validation_config.go Introduces MinIssueBodyLength and applies it to create_issue.body validation rules.
pkg/workflow/js/safe_outputs_tools.json Adds minLength: 20 to the compiler-embedded create_issue tool schema.
actions/setup/js/safe_outputs_tools.json Adds minLength: 20 to the runtime create_issue tool schema.
actions/setup/js/safe_output_type_validator.test.cjs Updates test config and adds explicit create_issue minLength coverage + adjusts existing tests to use longer bodies.
pkg/workflow/samples_validation_test.go Updates create_issue sample bodies to meet the new minimum length.
.github/workflows/daily-safeoutputs-git-simulator.lock.yml Regenerates the lockfile, introducing broad workflow/version/behavior changes beyond the stated PR goal.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 6/6 changed files
  • Comments generated: 3

Comment on lines 47 to +58
@@ -54,7 +55,7 @@ var ValidationConfig = map[string]TypeValidationConfig{
DefaultMax: 1,
Fields: map[string]FieldValidation{
"title": {Required: true, Type: "string", Sanitize: true, MaxLength: 128},
"body": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength},
"body": {Required: true, Type: "string", Sanitize: true, MaxLength: MaxBodyLength, MinLength: MinIssueBodyLength},
Comment on lines 51 to 108
@@ -75,9 +74,14 @@ jobs:
permissions:
actions: read
contents: read
env:
GH_AW_MAX_DAILY_AI_CREDITS: "500000"
outputs:
comment_id: ""
comment_repo: ""
daily_effective_workflow_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_exceeded == 'true' }}
daily_effective_workflow_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_threshold || '' }}
daily_effective_workflow_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }}
model: ${{ steps.generate_aw_info.outputs.model }}
@@ -87,34 +91,41 @@ jobs:
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }}
steps:
- name: Checkout actions folder
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
repository: github/gh-aw
sparse-checkout: |
actions
persist-credentials: false
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@v0.77.5
uses: ./actions/setup
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
env:
Comment on lines 12 to 17
},
"body": {
"type": "string",
"minLength": 20,
"description": "Detailed issue description in Markdown. Must be the final intended body \u2014 not a placeholder or test value. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate."
},
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel completed test quality analysis.

Testing noop

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

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

No ADR enforcement needed: PR #38114 does not have the 'implementation' label (has_implementation_label=false) and has only 5 new lines in business logic directories, which is below the 100-line threshold (requires_adr_by_default_volume=false). No custom .design-gate.yml config present.

@github-actions github-actions Bot mentioned this pull request Jun 9, 2026

@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 /tdd and /zoom-out — no blocking issues; three non-blocking observations surfaced.

📋 Key Themes & Highlights

Key Themes

  • Test boundary coverage: The new create_issue minLength test block covers == 20 (accept) and a short string (reject), but is missing the == 19 (reject) case that fully pins boundary semantics. Also missing a whitespace-only body test that already exists for create_discussion.
  • Schema inconsistency follow-up: This PR establishes the correct pattern — Go config constant + JSON schema minLength aligned. create_discussion has the same gap (Go: 64, JSON schema: none) and would benefit from the same fix.

Positive Highlights

  • ✅ Named constant MinIssueBodyLength = 20 follows the existing MinDiscussionBodyLength pattern exactly — easy to audit and change in one place
  • ✅ Both runtime and compiler-embedded JSON schemas updated together — the dual-write is easy to miss and was handled correctly
  • ✅ Three new boundary tests (< 20 reject, > 20 accept, == 20 accept) and the expect(body.length).toBe(20) assertion are a solid specification pattern
  • ✅ Existing test fixtures updated surgically to stay valid under the new constraint

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 537.8 AIC · ⌖ 13.5 AIC

const result = validateItem({ type: "create_issue", title: "Test Issue", body }, "create_issue", 1);

expect(result.isValid).toBe(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] The boundary test confirms == 20 is accepted, but has no companion test for == 19 (minLength − 1). Without it we cannot tell whether the implementation uses < 20 or <= 20 — a classic off-by-one gap.

💡 Suggested test at minLength − 1
it("should reject create_issue body at exactly minLength - 1", async () => {
  const { validateItem } = await import("./safe_output_type_validator.cjs");

  const body = "Exactly nineteen ch"; // 19 chars
  expect(body.length).toBe(19);
  const result = validateItem(
    { type: "create_issue", title: "Test Issue", body },
    "create_issue",
    1,
  );

  expect(result.isValid).toBe(false);
  expect(result.error).toContain("too short");
});

Pairing == 19 (reject) with == 20 (accept) fully pins the boundary semantics.

const result = validateItem({ type: "create_issue", title: "Test Issue", body }, "create_issue", 1);

expect(result.isValid).toBe(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] The create_discussion test suite covers the whitespace-trim edge case. The new create_issue block skips it. The validator trims before comparing (finalValue.trim().length < validation.minLength), so this behaviour is worth pinning explicitly for parity.

💡 Suggested whitespace test
it('should reject create_issue body that is only whitespace', async () => {
  const { validateItem } = await import('./safe_output_type_validator.cjs');

  // 25 spaces: raw length passes 20 but trimmed length is 0
  const result = validateItem(
    { type: 'create_issue', title: 'Test Issue', body: '                         ' },
    'create_issue',
    1,
  );

  expect(result.isValid).toBe(false);
  expect(result.error).toContain('too short');
});

Mirrors the existing create_discussion whitespace test and guards the trim-before-check contract.

},
"body": {
"type": "string",
"minLength": 20,

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.

[/zoom-out] This PR correctly aligns the create_issue JSON schema with its Go validation config. However, create_discussion has MinDiscussionBodyLength = 64 in safe_outputs_validation_config.go (line 48) but no corresponding "minLength": 64 in either safe_outputs_tools.json. The gap means:

  • Go sample validation (which compiles the JSON schema) will not catch short discussion bodies at compile time
  • The LLM tool description gives no hint about the length floor

A follow-up to add "minLength": 64 on create_discussion.body would close the same class of inconsistency this PR is fixing for create_issue.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 80/100 — Excellent

Analyzed 3 new test(s): 3 design, 0 implementation, 0 guideline violation(s). Existing tests in both JavaScript and Go were also updated to meet the new minLength: 20 constraint.

📊 Metrics & Test Classification (3 tests analyzed)
Metric Value
New/modified tests analyzed 3
✅ Design tests (behavioral contracts) 3 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 2 (67%)
Duplicate test clusters 0
Test inflation detected Yes (context note below)
🚨 Coding-guideline violations 0

Test Classification Details

Test File Classification Issues Detected
should reject create_issue body shorter than minLength safe_output_type_validator.test.cjs ✅ Design
should accept create_issue body that meets minLength safe_output_type_validator.test.cjs ✅ Design Happy-path only; single assertion (isValid: true)
should accept create_issue body at exact minLength safe_output_type_validator.test.cjs ✅ Design Boundary-value test ✅

Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 0 new test functions — 2 existing tests updated (test data strings only)
  • 🟨 JavaScript (*.test.cjs): 3 new tests (vitest)

Test Inflation Note

safe_output_type_validator.test.cjs grew by +37 lines while the production validator safe_output_type_validator.cjs was not modified — the feature is a 1-line schema addition in safe_outputs_tools.json. This gives a mechanical ratio of ~37:1. In context, the inflation is expected: a new minLength constraint needs reject/accept/boundary coverage, and the bulk of the additions are 3 well-scoped new test functions plus minor string updates to 7 existing tests to satisfy the new constraint. No test quality concern here.

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). All three new tests verify observable validation behavior directly.

📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

🧪 Test quality analysis by Test Quality Sentinel · 621.5 AIC · ⌖ 34.9 AIC ·

@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: 80/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%). All 3 new tests verify observable validation behavior (rejection, acceptance, and boundary acceptance for the new minLength constraint).

@pelikhan

pelikhan commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

@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.

REQUEST_CHANGES — one regression + two tests now verifying the wrong thing

One critical regression and two test-correctness issues need fixing before merge.

### Blocking issues

1. (Critical) sampleRuntimeExpressionPlaceholder breaks compile-time validation for runtime-expression bodies — see inline comment on safe_outputs_tools.json:15

Adding "minLength": 20 to the JSON schema makes it impossible to use a ${{ ... }} expression as a sample body for create_issue. The placeholder substituted during validateSafeOutputsSamples is "aw_sample" (9 chars), which fails the new schema constraint. Every existing and future workflow with body: "${{ github.event.inputs.description }}" in a create-issue sample will be rejected at compile time. create_discussion deliberately omits minLength from its JSON schema for this reason.

2. (High) Two unchanged tests now test the wrong failure mode — see inline comment on samples_validation_test.go:22

  • TestValidateSafeOutputsSamples_MissingRequired: body "Body without title" is 18 chars, now fails on minLength not missing-title. Test passes but no longer tests what it claims.
  • TestValidateSafeOutputsSamples_NonExpressionErrorsStillReported: body is a runtime expression → substituted to "aw_sample" (9 chars < 20) → fails on minLength not missing-title. Same problem.

3. (Medium) Missing TestCreateIssueBodyMinLength parity test — see inline comment on safe_outputs_validation_config.go:47

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.

🔎 Code quality review by PR Code Quality Reviewer · ⌖ 13.2 AIC

},
"body": {
"type": "string",
"minLength": 20,

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.

Regression: sampleRuntimeExpressionPlaceholder ("aw_sample", 9 chars) is shorter than this new minLength: 20, breaking compile-time validation for create_issue samples whose body is a ${{ ... }} runtime expression.

💡 Details and fix options

validateSafeOutputsSamples in samples_validation.go substitutes runtime expressions with a placeholder before schema validation. For plain string fields with no pattern/format, placeholderForType unconditionally returns "aw_sample" (9 chars). Now that the JSON Schema requires body.length >= 20, "aw_sample" fails that check, and any workflow like:

samples:
  - title: "Bug from ${{ github.event.inputs.component }}"
    body:  "${{ github.event.inputs.description }}"

will be rejected at gh aw compile time with a spurious too

{
"title": "Sample issue",
"body": "Sample body",
"body": "Sample issue body with enough detail.",

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.

Two other tests in this file were not updated and now silently test the wrong failure path.

💡 Details

1. TestValidateSafeOutputsSamples_MissingRequired (line 56)

"body": "Body without title",  // 18 chars — below the new minLength: 20

The test's intent is to verify that a missing title surfaces an error. After this PR, the JSON Schema also requires body.length >= 20, so the 18-char body now triggers a minLength failure before the missing-title check. The test still passes (there is still an error), but it no longer verifies what its comment claims. Fix: update the body to ≥ 20 chars, e.g., "Body text without title.".

2. TestValidateSafeOutputsSamples_NonExpressionErrorsStillReported (line 253)

"body": "${{ github.event.inputs.body }}",  // substituted → "aw_sample" (9 chars < 20)

This test verifies that a runtime expression on body does not mask a missing-title error. After this PR, the substituted placeholder "aw_sample" (9 chars) fails the new minLength: 20 JSON Schema constraint, so the error that surfaces is now body-too-short, not missing-title. The test still passes (error is still non-nil with the right key/index prefix), but the stated invariant is no longer being tested. This is also a symptom of the regression flagged on safe_outputs_tools.json line 15.

MaxBodyLength = 65000
MaxGitHubUsernameLength = 39
MaxGitHubTeamSlugLength = 100
MinIssueBodyLength = 20 // Minimum body length for create_issue to prevent placeholder-only submissions

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.

Missing parity test: no TestCreateIssueBodyMinLength analogous to the existing TestCreateDiscussionBodyMinLength.

💡 Details

safe_output_validation_config_test.go has TestCreateDiscussionBodyMinLength which asserts:

if bodyField.MinLength != MinDiscussionBodyLength { ... }

No equivalent test was added for create_issue. A future refactor that accidentally removes MinLength from the create_issue body field would be silently undetected. Add:

func TestCreateIssueBodyMinLength(t *testing.T) {
    config, ok := ValidationConfig["create_issue"]
    require.True(t, ok)
    body, ok := config.Fields["body"]
    require.True(t, ok)
    if body.MinLength != MinIssueBodyLength {
        t.Errorf("create_issue body MinLength = %d, want %d", body.MinLength, MinIssueBodyLength)
    }
}

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Ran the PR finisher pass and pushed fixes in 7a7a915 (runtime-expression minLength placeholder handling, missing minLength parity tests, and lockfile cleanup from the accidental broad update). Local validation passed (make fmt, make lint, make test-unit, make test, make recompile).

@pelikhan

pelikhan commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

@copilot review comments from GitHub-actions bot

Copilot AI commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

@copilot review comments from GitHub-actions bot

Reviewed the GitHub-actions bot feedback on this PR. The actionable items it raised are already addressed in 7a7a915 (minLength-aware runtime-expression placeholder handling, create_issue MinLength parity test, updated sample-validation tests, and lockfile scope cleanup). I don’t see additional required changes from the current bot comments.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

@copilot review all comments and address unresolved review feedback.

Generated by 👨‍🍳 PR Sous Chef · 77 AIC · ⌖ 1.06 AIC · ⊞ 17K ·

@pelikhan pelikhan merged commit 1db57bc into main Jun 9, 2026
44 checks passed
@pelikhan pelikhan deleted the copilot/daily-report-documentation-quality branch June 9, 2026 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Daily Report] Documentation Quality Report — 2026-06-09

3 participants