Skip to content

safe-outputs: add per-output opt-in normalization for backticked issue-closing keywords#36701

Open
Copilot wants to merge 12 commits into
mainfrom
copilot/safe-outputs-normalize-issue-closing-keywords
Open

safe-outputs: add per-output opt-in normalization for backticked issue-closing keywords#36701
Copilot wants to merge 12 commits into
mainfrom
copilot/safe-outputs-normalize-issue-closing-keywords

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jun 3, 2026

GitHub does not parse closing keywords inside code spans, so outputs like Closes \#123`` silently fail to auto-close issues. This PR adds a deterministic, opt-in normalization path that removes backticks only for recognized closing-keyword references, leaving unrelated inline code intact.

  • Ingestion/config plumbing

    • Moves configuration to per-output settings (instead of a top-level global flag).
    • Supports normalize-closing-keywords on:
      • safe-outputs.create-issue
      • safe-outputs.add-comment
      • safe-outputs.create-pull-request
    • Emits normalize_closing_keywords only within those output-type config objects in safe-outputs config.json.
    • Reads the flag per output type in NDJSON ingestion and passes it into type validation/normalization options.
  • Deterministic normalization scope

    • Implements targeted normalization in safe_output_type_validator.cjs for body fields of:
      • create_pull_request
      • create_issue
      • add_comment
    • Handles only recognized GitHub closing-keyword forms (whole-phrase and split backtick variants), including cross-repo refs.
    • Uses ordered pattern passes so malformed/non-matching markdown is not rewritten.
  • Representative behavior

    safe-outputs:
      create-pull-request:
        normalize-closing-keywords: true
    Closes `#123`                  -> Closes #123
    `Resolves GitHub/Repo#321`     -> Resolves GitHub/Repo#321
    `Fixed` `#99`                  -> Fixed #99
    Use `#123` for docs            -> unchanged
    

Copilot AI and others added 6 commits June 3, 2026 19:51
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>
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] Fix issue-closing keyword normalization in safe-outputs safe-outputs: add opt-in normalization for backticked issue-closing keywords Jun 3, 2026
Copilot AI requested a review from pelikhan June 3, 2026 19:58
@pelikhan pelikhan marked this pull request as ready for review June 3, 2026 19:59
Copilot AI review requested due to automatic review settings June 3, 2026 19:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an opt-in safe-outputs.normalize-closing-keywords flag that propagates from workflow YAML → generated safe-outputs config.json → NDJSON ingestion, enabling deterministic normalization that strips backticks only around recognized GitHub issue-closing keyword references in body fields.

Changes:

  • Introduces NormalizeClosingKeywords in SafeOutputsConfig, parses it from YAML, and merges it through imported workflow configs.
  • Emits normalize_closing_keywords into generated safe-outputs config.json when enabled and plumbs the flag into NDJSON ingestion validation options.
  • Implements and tests targeted backtick-stripping normalization for closing-keyword references in safe_output_type_validator.cjs.
Show a summary per file
File Description
pkg/workflow/compiler_types.go Adds NormalizeClosingKeywords to the workflow safe-outputs config type.
pkg/workflow/safe_outputs_config.go Parses normalize-closing-keywords from workflow frontmatter into SafeOutputsConfig.
pkg/workflow/imports.go Merges NormalizeClosingKeywords from imported configs when unset in the top-level config.
pkg/workflow/safe_outputs_config_generation.go Emits normalize_closing_keywords into generated safe-outputs config.json when enabled.
pkg/workflow/safe_outputs_config_generation_test.go Adds test coverage for config.json generation of normalize_closing_keywords.
pkg/workflow/safe_outputs_fix_test.go Extends merge tests to cover NormalizeClosingKeywords import precedence.
actions/setup/js/collect_ndjson_output.cjs Reads normalize_closing_keywords from config.json and passes it into output-type validation/normalization.
actions/setup/js/safe_output_type_validator.cjs Implements normalization that removes backticks around recognized closing-keyword references for selected types’ body fields.
actions/setup/js/safe_output_type_validator.test.cjs Adds JS tests verifying enabled/disabled behavior and non-interference with unrelated inline code.

Copilot's findings

Tip

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

  • Files reviewed: 9/9 changed files
  • Comments generated: 2

Comment on lines +176 to +180
// normalize-closing-keywords: when enabled, strip backticks around recognized
// issue-closing keyword references (e.g., Closes `#123`) during ingestion.
if data.SafeOutputs.NormalizeClosingKeywords != nil && *data.SafeOutputs.NormalizeClosingKeywords {
safeOutputsConfig["normalize_closing_keywords"] = true
}
Comment on lines +211 to +214
normalizeIssueClosingKeywords = expectedOutputTypes.normalize_closing_keywords === true;
// Remove global config keys so they are not treated as valid output types
delete expectedOutputTypes.max_bot_mentions;
delete expectedOutputTypes.normalize_closing_keywords;
* @param {string} content
* @returns {string}
*/
function normalizeIssueClosingKeywordBackticks(content) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot add dedicated unit tests + fuzzer tests

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added dedicated unit tests and fuzz-style invariant tests for closing-keyword normalization in actions/setup/js/safe_output_type_validator.test.cjs, including full keyword coverage, scope checks, idempotence, and adversarial backtick-heavy inputs. Addressed in commit fb9f9c6.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

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

No ADR enforcement needed: PR #36701 does not have the 'implementation' label and has 56 new lines of code in business logic directories (≤100 threshold).

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

⚠️ PR Code Quality Reviewer failed during code quality review.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

🧪 Test Quality Sentinel completed test quality analysis.

@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented Jun 3, 2026

@copilot normalize-closing-keywords should be specified on each safe-outputs separately

safe-outputs.normalize-closing-keywords # NO
safe-outputs.create-pull-request. normalize-closing-keywords # yes

Copilot AI and others added 2 commits June 3, 2026 20:31
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@github-actions github-actions Bot mentioned this pull request Jun 3, 2026
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 894.1K


it("should not modify unrelated backticked text when normalization is enabled", async () => {
const { validateItem } = await import("./safe_output_type_validator.cjs");

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] No test covers pattern 3 — the \keyword` #ref` variant (keyword backticked, reference bare). All four ordered patterns exist in the implementation but only three have corresponding test cases.

💡 Suggested test to add
it("should normalize a backticked keyword with a bare reference when enabled", async () => {
  const { validateItem } = await import("./safe_output_type_validator.cjs");

  const result = validateItem(
    { type: "add_comment", body: "`Closes` #456" },
    "add_comment", 1,
    { normalizeIssueClosingKeywords: true }
  );

  expect(result.isValid).toBe(true);
  expect(result.normalizedItem.body).toContain("Closes #456");
  expect(result.normalizedItem.body).not.toContain("`Closes`");
});

Without this, ISSUE_CLOSING_KEYWORD_BACKTICK_PATTERN (pattern 3) has no regression protection.

// https://docs.github.com/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue
const ISSUE_CLOSING_KEYWORDS = "fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved";
const ISSUE_REFERENCE_PATTERN = "(?:[a-zA-Z0-9_.-]+\\/[a-zA-Z0-9_.-]+)?#\\d+";
const ISSUE_CLOSING_WHOLE_SPAN_PATTERN = new RegExp(`\`(\\b(?:${ISSUE_CLOSING_KEYWORDS})\\b\\s+${ISSUE_REFERENCE_PATTERN})\``, "gi");
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.

[/grill-with-docs] NORMALIZE_CLOSER_BODY_TYPES excludes submit_pull_request_review, which also has a body field that agents populate. Is this intentional? If so, a brief inline comment explaining the scope decision would help future maintainers understand why review bodies are excluded.

💡 Context

If submit_pull_request_review bodies can also contain backticked closing keywords (e.g., an agent summarising a review and closing a related issue), omitting it means the normalization silently doesn't fire for that type. If that's a deliberate call (e.g., review bodies rarely carry closing keywords), document it:

// submit_pull_request_review intentionally excluded: review bodies rarely carry issue-closing keywords
const NORMALIZE_CLOSER_BODY_TYPES = new Set(["create_issue", "add_comment", "create_pull_request"]);

// Remove global config keys so they are not treated as valid output types
delete expectedOutputTypes.max_bot_mentions;
delete expectedOutputTypes.normalize_closing_keywords;
} catch (error) {
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 error path in the config-parsing catch block leaves normalizeIssueClosingKeywords = false silently. This is the correct safe default, but there's no test asserting this behavior. A config-parse failure could make an operator think normalization is running when it isn't.

💡 Suggestion

Either add a comment to the catch block to make the intent explicit:

} catch (error) {
  // normalizeIssueClosingKeywords stays false — safe default on config error
  const errorMsg = getErrorMessage(error);
  ...
}

Or, if the test harness allows, add a test that passes malformed config and asserts that normalization does not run.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

🧪 Test Quality Sentinel Report

Test Quality Score: 80/100 — Excellent

Analyzed 11 test(s): 11 design, 0 implementation, 0 guideline violation(s).

📊 Metrics & Test Classification (11 tests analyzed)
Metric Value
New/modified tests analyzed 11
✅ Design tests (behavioral contracts) 11 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 8 (73%)
Duplicate test clusters 0
Test inflation detected Yes (config_generation_test.go: +17 lines vs +6 production)
🚨 Coding-guideline violations 0

Test Classification Details

Test File Classification Issues Detected
should normalize a backticked issue reference when enabled safe_output_type_validator.test.cjs:218 ✅ Design None
should normalize a whole backticked closing phrase when enabled safe_output_type_validator.test.cjs:228 ✅ Design None
should normalize separately backticked keyword and reference when enabled safe_output_type_validator.test.cjs:238 ✅ Design None
should not normalize backticked closing references when disabled safe_output_type_validator.test.cjs:248 ✅ Design Edge case (disabled flag)
should not modify unrelated backticked text when normalization is enabled safe_output_type_validator.test.cjs:257 ✅ Design Edge case (precision boundary)
should leave malformed backticks unchanged safe_output_type_validator.test.cjs:269 ✅ Design Edge case (malformed input)
should normalize all supported closing keywords in whole and split backtick forms safe_output_type_validator.test.cjs:278 ✅ Design Edge case (exhaustive keyword coverage)
should only normalize body fields of configured tool types safe_output_type_validator.test.cjs:294 ✅ Design Edge case (field-type scoping)
TestGenerateSafeOutputsConfigNormalizeClosingKeywords safe_outputs_config_generation_test.go:189 ✅ Design None
NormalizeClosingKeywords imported when nil in main (table row) safe_outputs_fix_test.go:233 ✅ Design Edge case (nil import)
NormalizeClosingKeywords not overridden when set in main (table row) safe_outputs_fix_test.go:242 ✅ Design Edge case (precedence)

Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 3 tests — unit (//go:build !integration)
  • 🟨 JavaScript (*.test.cjs): 8 tests (vitest)

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). All 11 new tests verify observable behavioral contracts with good edge-case coverage. Minor note: safe_outputs_config_generation_test.go has a 2.8:1 line ratio vs its production file — acceptable given the new feature needed a thorough test for all keyword normalization inputs.

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

References: §26909551577

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 1.4M ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

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 excellent — 0% of new tests are implementation tests (threshold: 30%). All 11 new tests verify observable behavioral contracts with strong edge-case coverage across both JavaScript (vitest) and Go unit tests.

Copilot AI and others added 2 commits June 3, 2026 20:40
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jun 3, 2026

@copilot normalize-closing-keywords should be specified on each safe-outputs separately

safe-outputs.normalize-closing-keywords # NO...

Implemented in 7605b9a. normalize-closing-keywords is now per safe output type (for example safe-outputs.create-pull-request.normalize-closing-keywords) and no longer supported as a top-level safe-outputs key.

Copilot AI changed the title safe-outputs: add opt-in normalization for backticked issue-closing keywords safe-outputs: add per-output opt-in normalization for backticked issue-closing keywords Jun 3, 2026
@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented Jun 3, 2026

@copilot review all comments and apply copilot-review skill

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jun 3, 2026

@copilot review all comments and apply copilot-review skill

Reviewed all in-scope comments with the copilot-review workflow. The previously raised config-collision and ingestion concerns are already addressed by the per-output move in 7605b9a, and dedicated unit/fuzz coverage for normalization is in fb9f9c6. Revalidated targeted tests: go test ./pkg/workflow -run 'NormalizeClosingKeywords|SafeOutputsConfigNormalizeClosingKeywordsPerType' and npx vitest run safe_output_type_validator.test.cjs --no-file-parallelism (pass). No further code changes were needed.

@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented Jun 3, 2026

@copilot Lint js

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.

safe-outputs: deterministically normalize issue-closing keywords (un-backtick Closes #N) before create-pull-request / create-issue / add-comment

3 participants