Skip to content

add-comment: support hide-older-comments.match for exact multi-workflow comment minimization#37977

Merged
pelikhan merged 7 commits into
mainfrom
copilot/update-hide-older-comments
Jun 9, 2026
Merged

add-comment: support hide-older-comments.match for exact multi-workflow comment minimization#37977
pelikhan merged 7 commits into
mainfrom
copilot/update-hide-older-comments

Conversation

Copilot AI commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Some workflows emit multiple distinct comments per run, and current hide-older-comments behavior only targets one workflow ID, forcing brittle downstream cleanup. This change adds explicit exact-match workflow ID selection so older comments from multiple configured workflows can be minimized in one pass.

  • add-comment hide logic now supports multi-ID matching

    • Extended hide path to evaluate a set of workflow IDs (current workflow + configured matches) when selecting older comments.
    • Updated hide search/minimize flow for both issue/PR comments and discussion comments to use full-string workflow ID matches.
  • Config model: object form for hide-older-comments

    • Added support for:
      • hide-older-comments.enabled
      • hide-older-comments.match (array of workflow IDs)
    • Preserved existing boolean form for backward compatibility.
    • Added normalization/dedup of match entries before runtime use.
  • Compiler + handler config plumbing

    • Added parser handling for object-form hide-older-comments.
    • Propagated compiled match list to JS handler config as hide_older_comments_match.
  • Schema update

    • Expanded add-comment.hide-older-comments schema from templatable boolean to oneOf:
      • templatable boolean
      • object with enabled and match
  • Targeted coverage updates

    • Added JS tests validating:
      • exact match-based hiding across multiple workflow IDs
      • enabled: false behavior in object form
    • Added Go parser tests for object-form parsing/defaults and match extraction.
safe-outputs:
  add-comment:
    hide-older-comments:
      enabled: true
      match:
        - other_workflow
        - yet-another

Copilot AI and others added 4 commits June 9, 2026 00:21
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] Update hide-older-comments to support multiple comments add-comment: support hide-older-comments.match for exact multi-workflow comment minimization Jun 9, 2026
Copilot AI requested a review from pelikhan June 9, 2026 00:27
@pelikhan pelikhan marked this pull request as ready for review June 9, 2026 01:04
Copilot AI review requested due to automatic review settings June 9, 2026 01:04
@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

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

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

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 extends the add-comment safe-output handler so hide-older-comments can minimize older comments produced by multiple workflow IDs (exact workflow-id marker matches), not just the current workflow ID. It does this by introducing an object-form config (enabled + match) while keeping the existing boolean/templatable form for backward compatibility.

Changes:

  • Add Go-side config preprocessing to support hide-older-comments: { enabled, match }, extracting match into compiled handler config.
  • Update the JS add_comment handler to compute an effective set of workflow IDs (current + configured matches) and hide comments matching any of them.
  • Expand the JSON schema and add targeted Go/JS test coverage for object-form config and multi-ID hiding.
Show a summary per file
File Description
pkg/workflow/safe_outputs_handler_registry.go Propagates hide_older_comments_match into the handler config JSON passed to the JS runtime.
pkg/workflow/add_comment.go Adds parsing/preprocessing for object-form hide-older-comments and stores extracted match IDs in config.
pkg/workflow/add_comment_target_repo_test.go Adds Go test coverage for object-form parsing/defaults and match extraction.
pkg/parser/schemas/main_workflow_schema.json Extends hide-older-comments schema to oneOf (templatable boolean or object with enabled/match).
actions/setup/js/add_comment.test.cjs Adds JS tests validating multi-ID exact-match hiding and enabled: false behavior.
actions/setup/js/add_comment.cjs Implements multi-ID workflow marker matching in hide/minimize logic and object-form config support.

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 thread pkg/workflow/add_comment.go Outdated
Comment on lines +18 to +23
@@ -17,11 +19,12 @@ type AddCommentsConfig struct {
TargetRepoSlug string `yaml:"target-repo,omitempty"` // Target repository in format "owner/repo" for cross-repository comments
AllowedRepos []string `yaml:"allowed-repos,omitempty"` // List of additional repositories that comments can be added to (additionally to the target-repo)
HideOlderComments *string `yaml:"hide-older-comments,omitempty"` // When true, minimizes/hides all previous comments from the same workflow before creating the new comment
AllowedReasons []string `yaml:"allowed-reasons,omitempty"` // List of allowed reasons for hiding older comments (default: all reasons allowed)
Issues *bool `yaml:"issues,omitempty"` // When false, excludes issues:write permission and issues from event condition. Default (nil or true) includes issues:write.
PullRequests *bool `yaml:"pull-requests,omitempty"` // When false, excludes pull-requests:write permission and PRs from event condition. Default (nil or true) includes pull-requests:write.
Discussions *bool `yaml:"discussions,omitempty"` // When false, excludes discussions:write permission. Default (nil or true) includes discussions:write.
Footer *string `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted but XML markers are kept.
HideOlderCommentsMatch []string `yaml:"hide-older-comments-match,omitempty"`
AllowedReasons []string `yaml:"allowed-reasons,omitempty"` // List of allowed reasons for hiding older comments (default: all reasons allowed)
Comment on lines 174 to +178
* @param {number} issueNumber - Issue/PR number
* @param {string} workflowId - Workflow ID to search for
* @param {string[]} workflowIds - Workflow IDs to search for
* @returns {Promise<Array<{id: number, node_id: string, body: string}>>}
*/
async function findCommentsWithTrackerId(github, owner, repo, issueNumber, workflowId) {
async function findCommentsWithTrackerId(github, owner, repo, issueNumber, workflowIds) {
Comment on lines 216 to +220
* @param {number} discussionNumber - Discussion number
* @param {string} workflowId - Workflow ID to search for
* @param {string[]} workflowIds - Workflow IDs to search for
* @returns {Promise<Array<{id: string, body: string}>>}
*/
async function findDiscussionCommentsWithTrackerId(github, owner, repo, discussionNumber, workflowId) {
async function findDiscussionCommentsWithTrackerId(github, owner, repo, discussionNumber, workflowIds) {
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (>100 new lines across pkg/workflow and actions/setup/js) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/37977-object-form-hide-older-comments-with-match-list.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 — confirm the decision rationale, refine the alternatives you actually considered, and adjust consequences.
  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-37977: Object Form for hide-older-comments with Multi-Workflow Match List

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

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

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 84.5 AIC · ⌖ 9.67 AIC ·

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100 — Excellent

Analyzed 4 test(s) across 2 files: 4 design tests, 0 implementation tests, 0 guideline violations.

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

Inflation Ratio

Test File Test Lines Added Production Lines Added Ratio
add_comment.test.cjs 81 49 1.65 ✅
add_comment_target_repo_test.go 47 51 0.92 ✅

Test Classification Details

Test File Classification Notes
it("should hide comments matching hide-older-comments.match values") actions/setup/js/add_comment.test.cjs ✅ Design Asserts which node IDs are hidden vs. excluded; includes boundary (not.toContain)
it("should respect hide_older_comments.enabled when object config is used") actions/setup/js/add_comment.test.cjs ✅ Design Verifies enabled: false disables the feature; mocks are GitHub API (external I/O)
Go table row: "hide-older-comments object form defaults enabled and parses match list" pkg/workflow/add_comment_target_repo_test.go ✅ Design Verifies config parsing: default-enabled + multi-value match list
Go table row: "hide-older-comments object form supports explicit enabled false" pkg/workflow/add_comment_target_repo_test.go ✅ Design Verifies config parsing: explicit enabled: false is preserved alongside match list

Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 2 table rows in TestAddCommentsConfigHideOlderComments — unit (//go:build !integration) ✅
  • 🟨 JavaScript (*.test.cjs): 2 tests (vitest)

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). All new tests verify observable behavioral contracts.

📖 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: §27177131959

🧪 Test quality analysis by Test Quality Sentinel · 185.8 AIC · ⌖ 27.3 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: 100/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%). All 4 new test scenarios (2 JavaScript, 2 Go table rows) verify observable behavioral contracts with edge-case coverage and no guideline violations.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Skills-Based Review 🧠

Applied /tdd, /diagnose, and /grill-with-docs — 5 non-blocking suggestions posted as inline comments.

📋 Key Themes & Highlights

Key Themes

  1. Test gap — compiled production path (add_comment.cjs lines 405-406): the config.hide_older_comments_match flat-key branch is what Go-compiled workflows use at runtime, but all new tests exercise only the object-direct JS path. The production wiring added in safe_outputs_handler_registry.go is untested end-to-end.

  2. Test gap — normalisation helpers: normalizeWorkflowIdList trim/dedup behaviour and the "no env workflow ID" path are not covered.

  3. Silent failure on invalid config (add_comment.go line 42): a bad hide-older-comments.enabled value silently drops the entire add-comment config with no user-visible compile error.

  4. Undocumented internal YAML field (add_comment.go line 22): HideOlderCommentsMatch uses a yaml: tag for an internal preprocessed key that looks user-facing but is not in the schema.

Positive Highlights

  • ✅ Clean two-path normalisation preserves full backward compatibility with the boolean form.
  • normalizeWorkflowIdList is a nicely self-contained pure function (Set-based dedup, trim, blank filter).
  • preprocessHideOlderCommentsConfig in Go mirrors the JS logic, keeping both layers in sync.
  • ✅ Schema additionalProperties: false on the object branch correctly prevents silent user typos.
  • ✅ Go tests cover object-form defaults and explicit enabled: false well.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 448.3 AIC · ⌖ 13.6 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.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 448.3 AIC · ⌖ 13.6 AIC

const hideOlderCommentsMatch = Array.isArray(hideOlderCommentsConfig?.match)
? normalizeWorkflowIdList(hideOlderCommentsConfig.match)
: Array.isArray(config.hide_older_comments_match)
? normalizeWorkflowIdList(config.hide_older_comments_match)

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 config.hide_older_comments_match branch (lines 405–406) — the actual compiled production path — has no test coverage. When Go compiles a workflow, the registry sends hide_older_comments_match as a flat top-level key with hide_older_comments as the string "true"; this else-branch is what runs in production. The existing tests only exercise the object-direct path (hide_older_comments is an object), leaving the live code path untested.

💡 Suggested test (compiled config form)
// mirrors what the Go registry actually sends to the JS handler
const handler = await eval(`(async () => {
  ${addCommentScript};
  return await main({
    hide_older_comments: "true",
    hide_older_comments_match: ["other_workflow"]
  });
})()`);
const result = await handler({ type: "add_comment", body: "compiled path test" }, {});
expect(hiddenNodeIds).toContain("IC_kwDOTest11"); // other_workflow

This mirrors the actual runtime values set by AddTemplatableBool + AddStringSlice in safe_outputs_handler_registry.go.


if err := preprocessHideOlderCommentsConfig(configData, addCommentLog); err != nil {
addCommentLog.Printf("Invalid hide-older-comments configuration: %v", err)
return nil

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.

[/diagnose] An invalid hide-older-comments.enabled value causes parseCommentsConfig to return nil, silently dropping the entire add-comment configuration. The error is only logged internally; the workflow author gets no compile-time diagnostic explaining why their add-comment handler disappeared.

💡 Suggestion

Consider following the existing preprocessBoolFieldAsString pattern more closely — those also return nil on error, but the enabled case is harder to diagnose because it removes the whole handler rather than one field. At minimum, upgrade the log to a user-visible error (e.g. via the compiler error collection) or add a compile step that rejects the workflow with a descriptive message like "hide-older-comments.enabled must be a boolean or ${{ expr }}".

ids
.filter(id => typeof id === "string")
.map(id => id.trim())
.filter(Boolean)

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] normalizeWorkflowIdList trims whitespace and deduplicates entries, but neither behaviour is exercised by any test. A user authoring match: [" wf-a ", "wf-a"] should get ["wf-a"] — both the dedup and the trim are load-bearing for correctness.

💡 Suggested unit-level assertions

Since this is a pure function, it can be tested directly (or via a small dedicated test block without needing a full mock setup):

// trim
normalizeWorkflowIdList(["  wf-a  ", "wf-b "]) // → ["wf-a", "wf-b"]
// dedup
normalizeWorkflowIdList(["wf-a", "wf-a", "wf-b"]) // → ["wf-a", "wf-b"]
// empty-string / whitespace-only filtered
normalizeWorkflowIdList(["", "  ", "wf-a"]) // → ["wf-a"]

} else if (workflowId) {
await hideOlderComments(githubClient, repoParts.owner, repoParts.repo, itemNumber, workflowId, isDiscussion);
} else {
const hideWorkflowIds = normalizeWorkflowIdList([workflowId, ...hideOlderCommentsMatch]);

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] When GH_AW_WORKFLOW_ID is not set, workflowId is "" (line 452) and is correctly filtered out by normalizeWorkflowIdList. This means hiding is driven entirely by hideOlderCommentsMatch. There is no test for this path — confirming that { match: ["target-wf"] } alone hides the right comments when the env var is absent.

💡 Suggested test
it("should hide match-list comments when GH_AW_WORKFLOW_ID is not set", async () => {
  delete process.env.GH_AW_WORKFLOW_ID;
  // ... setup mocks ...
  const handler = await eval(`... main({ hide_older_comments: { match: ["target-wf"] } }) ...`);
  const result = await handler({ type: "add_comment", body: "test" }, {});
  expect(hiddenNodeIds).toContain(/* target-wf comment node */);
  expect(hiddenNodeIds).not.toContain(/* unrelated comment node */);
});

Comment thread pkg/workflow/add_comment.go Outdated
PullRequests *bool `yaml:"pull-requests,omitempty"` // When false, excludes pull-requests:write permission and PRs from event condition. Default (nil or true) includes pull-requests:write.
Discussions *bool `yaml:"discussions,omitempty"` // When false, excludes discussions:write permission. Default (nil or true) includes discussions:write.
Footer *string `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted but XML markers are kept.
HideOlderCommentsMatch []string `yaml:"hide-older-comments-match,omitempty"`

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] The yaml:"hide-older-comments-match" tag looks like a user-facing YAML key, but this field is an internal intermediate value injected by preprocessHideOlderCommentsConfig — it does not appear in the schema and cannot be set directly in workflow YAML. Future contributors may be confused about why it exists as a struct tag vs. a typed nested field.

💡 Suggested comment
// HideOlderCommentsMatch is an internal field populated by preprocessHideOlderCommentsConfig
// from the hide-older-comments.match array. Not a user-facing YAML key; the schema exposes
// it only via the hide-older-comments object form.
HideOlderCommentsMatch []string `yaml:"hide-older-comments-match,omitempty"`

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

``
Refresh the branch and rerun checks after the review fixes land.

Generated by 👨‍🍳 PR Sous Chef · 33.4 AIC · ⌖ 0.943 AIC · ⊞ 17.1K ·

@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 · 33.4 AIC · ⌖ 0.943 AIC · ⊞ 17.1K ·

@pelikhan

pelikhan commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

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

Done in 1125206. Local validation and final review passed. CI on this agent-pushed head is unverified until a maintainer re-triggers it before merge.

@pelikhan

pelikhan commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

@copilot review all review comments

Copilot AI commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

@copilot review all review comments

Reviewed the in-scope Copilot review comments against the current head. All three are already satisfied in 1125206: pkg/workflow/add_comment.go now documents HideOlderCommentsMatch, and the JSDoc on both findCommentsWithTrackerId and findDiscussionCommentsWithTrackerId now describes matching any workflow ID from the provided list. No additional code changes were needed.

@pelikhan pelikhan merged commit b0e77b7 into main Jun 9, 2026
37 checks passed
@pelikhan pelikhan deleted the copilot/update-hide-older-comments branch June 9, 2026 02:14
github-actions Bot added a commit that referenced this pull request Jun 9, 2026
Added Hide Older Comments (hide-older-comments) entry covering the new
object form with match field for multi-workflow comment minimization
(introduced in #37977).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
github-actions Bot added a commit that referenced this pull request Jun 9, 2026
…erivation

- Document object form of hide-older-comments with match list (#37977)
- Document github-app owner derivation when owner is omitted (#37976)
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.

Update hide-older-comments in add-comment to support multiple comments

3 participants