Skip to content

Deduplicate skip-query gate logic for setup action guards#43480

Open
pelikhan with Copilot wants to merge 3 commits into
mainfrom
copilot/duplicate-code-skip-gate-mirror-logic
Open

Deduplicate skip-query gate logic for setup action guards#43480
pelikhan with Copilot wants to merge 3 commits into
mainfrom
copilot/duplicate-code-skip-gate-mirror-logic

Conversation

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

check_skip_if_match.cjs and check_skip_if_no_match.cjs carried mirrored implementations of the same skip-query gate (config validation, search execution, threshold evaluation, output/summary wiring). This change extracts the shared gate flow into one helper and leaves each file as a policy wrapper.

  • Shared gate extraction

    • Added runSkipQueryGate(...) in actions/setup/js/check_skip_if_helpers.cjs.
    • Centralizes:
      • required env validation (GH_AW_SKIP_QUERY, GH_AW_WORKFLOW_NAME)
      • threshold parsing/validation
      • query construction + search call
      • common success/error handling
      • denial summary emission
  • Policy-specific wrappers retained

    • check_skip_if_match.cjs now passes:
      • comparator: totalCount >= threshold
      • output key: skip_check_ok
      • match-specific warning/success/summary text
    • check_skip_if_no_match.cjs now passes:
      • comparator: totalCount < threshold
      • output key: skip_no_match_check_ok
      • no-match-specific warning/success/summary text
  • Focused helper coverage

    • Extended check_skip_if_helpers.test.cjs with runSkipQueryGate tests for:
      • env-var-aware threshold validation errors
      • policy-driven skip decision and output routing
await runSkipQueryGate({
  thresholdEnvVar: "GH_AW_SKIP_MAX_MATCHES",
  shouldSkip: (totalCount, threshold) => totalCount >= threshold,
  outputName: "skip_check_ok",
  warningMessage: (count, t) => `...threshold: ${t}...`,
});

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor skip gate mirror logic to reduce duplication Deduplicate skip-query gate logic for setup action guards Jul 5, 2026
Copilot AI requested a review from pelikhan July 5, 2026 06:46
@pelikhan pelikhan marked this pull request as ready for review July 5, 2026 07:00
Copilot AI review requested due to automatic review settings July 5, 2026 07:00
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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

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

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based 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 refactors the setup-action “skip query gate” logic by extracting the shared control flow into a single helper (runSkipQueryGate) and leaving the two entrypoints (skip-if-match / skip-if-no-match) as small policy wrappers. This reduces duplicated validation/search/summary wiring while preserving the existing behavior and output contracts.

Changes:

  • Added runSkipQueryGate(...) to centralize env validation, threshold parsing, query execution, output setting, and denial summary emission.
  • Updated check_skip_if_match.cjs and check_skip_if_no_match.cjs to call the shared helper with policy-specific comparator + messaging.
  • Extended unit tests to cover helper behavior (threshold validation errors and “skip” decision/output routing).
Show a summary per file
File Description
actions/setup/js/check_skip_if_helpers.cjs Adds runSkipQueryGate and relocates common skip gate logic into the helper module.
actions/setup/js/check_skip_if_match.cjs Replaces duplicated gate logic with a policy wrapper for the “match” comparator and messages.
actions/setup/js/check_skip_if_no_match.cjs Replaces duplicated gate logic with a policy wrapper for the “no match” comparator and messages.
actions/setup/js/check_skip_if_helpers.test.cjs Adds targeted tests for runSkipQueryGate validation and policy-driven skip behavior.

Review details

Tip

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

  • Files reviewed: 4/4 changed files
  • Comments generated: 0
  • Review effort level: Low

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100 — Excellent

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

📊 Metrics (2 tests)
Metric Value
Analyzed 2 (Go: 0, JS: 2)
✅ Design 2 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 2 (100%)
Duplicate clusters 0
Inflation No (test +62 lines, prod +71 lines → 0.87:1)
🚨 Violations 0
Test File Classification Issues
uses thresholdEnvVar in validation errors check_skip_if_helpers.test.cjs design_test / high_value None
applies policy decision and output name when skip condition matches check_skip_if_helpers.test.cjs design_test / high_value None

Verdict

Passed. 0% implementation tests (threshold: 30%). Both new tests verify user-visible behavioral contracts: error message content with the correct env var name, and GitHub Actions output value when the skip policy fires.

Warning

Firewall blocked 1 domain

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

  • awmgmcpg

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

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 30.8 AIC · ⌖ 15.5 AIC · ⊞ 6.8K ·
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. 0% implementation tests (threshold: 30%).

@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 /codebase-design — requesting changes on test coverage gaps and one interface clarity issue.

📋 Key Themes & Highlights

Key Themes

  • Test coverage gaps: The new runSkipQueryGate helper has 4 untested paths out of 5 branches (success path, skipQuery missing, workflowName missing, search API throws). Only the skip-condition-met path and invalid threshold are covered.
  • Dead validation: workflowName is validated at startup but never read inside the function body — either use it or remove it from the interface.
  • Test structure: runSkipQueryGate describe block is nested inside the buildSearchQuery describe block, which makes the test hierarchy misleading in reporters.
  • Long destructure: 13-field single-line destructure makes the only optional field (thresholdStr) invisible without horizontal scrolling.

Positive Highlights

  • ✅ Excellent refactoring: the callback-based parameterization (shouldSkip, warningMessage, etc.) is a clean way to invert the policy without leaking gate-internal state
  • ✅ The two wrapper files are now minimal and easy to audit at a glance
  • thresholdEnvVar passed to the helper to produce actionable error messages — good UX detail
  • buildSearchQuery already had solid test coverage and this PR leaves it intact

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 71.8 AIC · ⌖ 6.88 AIC · ⊞ 6.6K
Comment /matt to run again

return;
}

if (!workflowName) {

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] workflowName is validated here but never referenced inside the function — dead validation.

💡 Fix or remove

The extracted helper checks workflowName is present but then never uses it — not in logs, not in the denial summary, nowhere. Either incorporate it (e.g., log the active workflow) or remove the field from the interface and all callers.

// Option A: use it in a log
core.info(`Running ${checkLabel} gate for workflow: ${workflowName}`);

// Option B: remove from interface and callers
// — eliminates the misleading contract

A required option that is validated but silently discarded makes the interface harder to reason about.

@copilot please address this.

* }} options
*/
async function runSkipQueryGate(options) {
const { skipQuery, workflowName, thresholdStr = "1", thresholdEnvVar, thresholdLabel, checkLabel, outputName, skipScope, shouldSkip, warningMessage, successMessage, denialSummaryMessage, denialSummaryNextStep } = options;

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 destructure on line 44 is a single 180-character line — the interface is deep but its surface area is not visible at a glance.

💡 Consider breaking up the destructure

With 13 fields, the one-liner is hard to scan for an optional field vs a required one:

const {
  skipQuery,
  workflowName,
  thresholdStr = "1",
  thresholdEnvVar,
  thresholdLabel,
  checkLabel,
  outputName,
  skipScope,
  shouldSkip,
  warningMessage,
  successMessage,
  denialSummaryMessage,
  denialSummaryNextStep,
} = options;

This makes the default value for thresholdStr visible without having to scroll and clearly separates optional from required fields.

@copilot please address this.

});
});

describe("check_skip_if_helpers.cjs - runSkipQueryGate", () => {

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 runSkipQueryGate describe block is nested inside the buildSearchQuery describe block — this looks like a scoping accident.

💡 Move to the top-level describe

The runSkipQueryGate describe group opens at line 72 inside the describe('check_skip_if_helpers.cjs - buildSearchQuery', ...) block (line 41) and closes at line 132, but the scope: default (repo-scoped) group that follows at line 134 is a child of buildSearchQuery, not a sibling of runSkipQueryGate. This makes the nesting hierarchy misleading and may cause test reporters to nest runSkipQueryGate under buildSearchQuery in their output.

Move runSkipQueryGate to the top-level, e.g.:

describe("check_skip_if_helpers.cjs - buildSearchQuery", () => { ... });

describe("check_skip_if_helpers.cjs - runSkipQueryGate", () => { ... });

@copilot please address this.

expect(global.github.rest.search.issuesAndPullRequests).not.toHaveBeenCalled();
});

it("applies policy decision and output name when skip condition matches", async () => {

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 the success path (skip condition NOT met) — only the skip/denial branch is verified.

💡 Add a success-path test

The new runSkipQueryGate tests cover:

  1. Invalid threshold — setFailed called, search never reached
  2. Skip condition met — setOutput with "false", warning emitted

Missing: skip condition not met (totalCount below threshold). This is the most common production path and should assert setOutput(outputName, "true") and successMessage invoked:

it('sets output to true and logs success when skip condition is not met', async () => {
  global.github.rest.search.issuesAndPullRequests.mockResolvedValue({ data: { total_count: 0 } });
  const { runSkipQueryGate } = await loadModule();
  const successMessage = vi.fn(() => 'all clear');

  await runSkipQueryGate({
    skipQuery: 'is:issue is:open',
    workflowName: 'test-workflow',
    thresholdStr: '1',
    thresholdEnvVar: 'GH_AW_SKIP_MAX_MATCHES',
    thresholdLabel: 'Maximum matches threshold',
    checkLabel: 'skip-if-match',
    outputName: 'skip_check_ok',
    skipScope: undefined,
    shouldSkip: (count, t) => count >= t,
    warningMessage: () => '',
    successMessage,
    denialSummaryMessage: () => '',
    denialSummaryNextStep: '',
  });

  expect(mockCore.setOutput).toHaveBeenCalledWith('skip_check_ok', 'true');
  expect(successMessage).toHaveBeenCalledWith(0, 1);
});

@copilot please address this.

};
});

it("uses thresholdEnvVar in validation errors", async () => {

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 the skipQuery missing or workflowName missing early-exit paths in the new runSkipQueryGate.

💡 Add early-exit guard tests

The function has three early-exit guards (missing skipQuery, missing workflowName, bad threshold). Only the third is tested. Specs for the first two document the contract and prevent regressions if the guards are ever reordered:

it('fails with config error when skipQuery is undefined', async () => {
  const { runSkipQueryGate } = await loadModule();
  await runSkipQueryGate({ skipQuery: undefined, workflowName: 'wf', ... });
  expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining('GH_AW_SKIP_QUERY not specified'));
});

it('fails with config error when workflowName is undefined', async () => {
  const { runSkipQueryGate } = await loadModule();
  await runSkipQueryGate({ skipQuery: 'is:issue', workflowName: undefined, ... });
  expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining('GH_AW_WORKFLOW_NAME not specified'));
});

@copilot please address this.

expect(global.github.rest.search.issuesAndPullRequests).not.toHaveBeenCalled();
});

it("applies policy decision and output name when skip condition matches", async () => {

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 the API error path (search throws) — the catch block in runSkipQueryGate is untested.

💡 Add an error-path test
it('calls setFailed with ERR_API prefix when search throws', async () => {
  global.github.rest.search.issuesAndPullRequests.mockRejectedValue(new Error('rate limited'));
  const { runSkipQueryGate } = await loadModule();

  await runSkipQueryGate({
    skipQuery: 'is:issue is:open',
    workflowName: 'test-workflow',
    thresholdStr: '1',
    thresholdEnvVar: 'GH_AW_SKIP_MAX_MATCHES',
    thresholdLabel: 'Maximum matches threshold',
    checkLabel: 'skip-if-match',
    outputName: 'skip_check_ok',
    skipScope: undefined,
    shouldSkip: () => false,
    warningMessage: () => '',
    successMessage: () => '',
    denialSummaryMessage: () => '',
    denialSummaryNextStep: '',
  });

  expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining('ERR_API'));
  expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining('rate limited'));
});

Without this, a future refactor of the catch block could silently lose the error code.

@copilot please address this.

@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: Deduplicate skip-query gate logic

The extraction of runSkipQueryGate into a shared helper is a clean, correct refactor — the policy wrappers are now minimal and symmetric, and the helper correctly centralises env validation, threshold parsing, search execution, and output/summary wiring. Logic and security look correct.

Two actionable issues found:

🔴 Test suite nesting bug (correctness)

The runSkipQueryGate describe block is inadvertently nested inside the outer buildSearchQuery describe block. This means Vitest/Jest reporters will emit results under a misleading path (buildSearchQuery > runSkipQueryGate > ...), and future contributors may not realize the two suites are unrelated. The scope: default describe that follows is also inside the outer block, making the existing structure more confusing after this change.

🟡 Non-blocking suggestions

  • Duplicate loadModule helper — declared once per describe; a single shared helper at the file top would reduce duplication.
  • Missing success-path test — the new tests only cover the skip branch; a test for the non-skip branch (shouldSkip = false → setOutput(outputName, "true")) would give full coverage.
  • Long destructure line — the options bag is unpacked in a single 170+ char line; multi-line formatting would improve diff readability.
  • Implicit global ambient annotationcore, github, context are used as globals in the helper without annotation; the existing /// <reference types="@actions/github-script" /> covers it, but a brief comment in the new function noting the runtime globals dependency would aid discoverability.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 69.8 AIC · ⌖ 5.35 AIC · ⊞ 4.8K

});
});

describe("check_skip_if_helpers.cjs - runSkipQueryGate", () => {

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 runSkipQueryGate describe block is nested inside the outer buildSearchQuery describe block (opened at line 41), so test reports show these as buildSearchQuery > runSkipQueryGate > .... Move it to a top-level sibling to keep the two test suites independent and make failure output unambiguous.

// Close the buildSearchQuery describe block, then open a sibling:
describe("check_skip_if_helpers.cjs - runSkipQueryGate", () => {
  // ...
});

@copilot please address this.

};
});

it("uses thresholdEnvVar in validation errors", async () => {

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 loadModule inside the runSkipQueryGate describe is declared identically to the one in the buildSearchQuery describe (line 43), but returns a different type annotation. Since CJS modules are cached, calling import() multiple times within the same test run returns the same instance — this is fine for correctness but the duplicate loadModule helper adds confusion. Consider hoisting a single shared loadModule to the file's top level, or at least document that the module cache makes repeated imports safe.

@copilot please address this.

const { skipQuery, workflowName, thresholdStr = "1", thresholdEnvVar, thresholdLabel, checkLabel, outputName, skipScope, shouldSkip, warningMessage, successMessage, denialSummaryMessage, denialSummaryNextStep } = options;

if (!skipQuery) {
core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_SKIP_QUERY not specified.`);

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 runSkipQueryGate function uses core, github, and context as globals — consistent with how the callers work under @actions/github-script. However, those globals are not declared or imported in this helper module, so static analysis (and IDE tooling) cannot verify them. Consider adding a brief comment referencing the expected ambient globals, or — if the module is ever tested in isolation without a global-script harness — accept them as implicit. The test file's beforeEach does set them up, so tests are fine; this is a documentation/discoverability concern only.

@copilot please address this.

const { skipQuery, workflowName, thresholdStr = "1", thresholdEnvVar, thresholdLabel, checkLabel, outputName, skipScope, shouldSkip, warningMessage, successMessage, denialSummaryMessage, denialSummaryNextStep } = options;

if (!skipQuery) {
core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_SKIP_QUERY not specified.`);

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 destructuring line is very long (170+ chars) — the entire options bag unpacked on one line. Breaking it into a multi-line destructure would aid readability and make diffs easier to review if individual parameters change later.

const {
  skipQuery, workflowName, thresholdStr = "1",
  thresholdEnvVar, thresholdLabel, checkLabel, outputName, skipScope,
  shouldSkip, warningMessage, successMessage,
  denialSummaryMessage, denialSummaryNextStep,
} = options;

@copilot please address this.

expect(global.github.rest.search.issuesAndPullRequests).not.toHaveBeenCalled();
});

it("applies policy decision and output name when skip condition matches", async () => {

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 test "applies policy decision and output name when skip condition matches" verifies the skip path but there is no corresponding test for the success (non-skip) path — i.e., when shouldSkip returns false. Consider adding a test that asserts setOutput is called with "true" and warning is not called, to fully cover the happy path.

@copilot please address this.

@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 — 3 issues must be addressed before merge

The refactor direction is sound, but the implementation has a correctness bug, a test structure bug, and a test coverage gap that need fixing.

### Blocking findings (3)

1. validated but never used ()
The field is required, destructured, checked for existence — then completely ignored. Either it belongs in a log line or the denial summary, or it should be removed from the options contract and stop being required from callers. As-is, the refactor has entrenched a pre-existing dead-validation bug across both call sites.

2. describe block is wrongly nested inside describe block ()
All new tests run under the wrong test suite label in CI output. The duplicate inside the nested block is a direct symptom of copy-paste under the wrong parent.

3. Skip-path test doesn't assert was called ()
/ mocks are wired but unasserted. Dropping the denial summary call in a future change will pass this test silently.

🔎 Code quality review by PR Code Quality Reviewer · 156.8 AIC · ⌖ 5.99 AIC · ⊞ 5.4K
Comment /review to run again

return;
}

if (!workflowName) {

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.

workflowName is validated but never used — dead validation or a latent bug from the pre-refactor code.

💡 Details

workflowName is destructured, checked with if (!workflowName), and then never referenced again anywhere in the function body. It doesn't appear in the search query, the log output, the denial summary message, or any other call.

Two possible interpretations:

  1. The validation is intentional but wrong: callers must supply GH_AW_WORKFLOW_NAME for no functional reason — the helper ignores it entirely.
  2. The field was meant to be used: it was likely supposed to appear in the denial summary or scoped query and was silently dropped during the refactor.

The pre-refactor code had the same pattern, but this PR cements it into a shared helper used by both call sites. Either use workflowName (e.g., in the denial summary or log line) or remove it from the options schema and eliminate the GH_AW_WORKFLOW_NAME requirement entirely.

});
});

describe("check_skip_if_helpers.cjs - runSkipQueryGate", () => {

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 runSkipQueryGate describe block is incorrectly nested inside the buildSearchQuery describe block, corrupting test output structure.

💡 Details

The describe('check_skip_if_helpers.cjs - runSkipQueryGate', ...) block opens at line 72 inside the describe('check_skip_if_helpers.cjs - buildSearchQuery', ...) block that opened at line 41. As a result:

  • All runSkipQueryGate tests are reported in CI under the buildSearchQuery test suite label, making failures misleading.
  • The inner const loadModule = () => import('./check_skip_if_helpers.cjs') at line 74 is an exact duplicate of the one at line 43, shadowing it pointlessly — a clear artifact of copy-paste inside the wrong nesting level.
  • The describe('scope: default (repo-scoped)', ...) block at line 134 is also still inside the outer buildSearchQuery describe (correct by coincidence, but confusing).

Fix: close the outer buildSearchQuery describe before opening runSkipQueryGate, and remove the duplicate loadModule declaration:

expect(global.github.rest.search.issuesAndPullRequests).not.toHaveBeenCalled();
});

it("applies policy decision and output name when skip condition matches", async () => {

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 skip-path test does not assert writeDenialSummary was called — a critical side effect goes unverified.

💡 Details

The test at line 109 ('applies policy decision and output name when skip condition matches') checks core.warning and core.setOutput, but never asserts that the denial summary was written. The writeDenialSummary call at helper line 80 is a significant side effect: it writes to the GitHub Actions job summary and is the operator-visible explanation for why a workflow was blocked.

The mocks for core.summary.addRaw and core.summary.write are already in place via mockCore.summary — but they are never asserted on.

Add assertions such as:

expect(mockCore.summary.addRaw).toHaveBeenCalled();
expect(mockCore.summary.write).toHaveBeenCalled();

Without this, a future regression that silently drops the writeDenialSummary call will not be caught by this test suite.

* }} options
*/
async function runSkipQueryGate(options) {
const { skipQuery, workflowName, thresholdStr = "1", thresholdEnvVar, thresholdLabel, checkLabel, outputName, skipScope, shouldSkip, warningMessage, successMessage, denialSummaryMessage, denialSummaryNextStep } = options;

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.

Double-defaulting thresholdStr creates ambiguous ownership and swallows missing-config errors silently.

💡 Details

The helper destructures thresholdStr = '1' as a default at line 44, and the callers also apply their own defaults at the env-var layer (e.g., GH_AW_SKIP_MAX_MATCHES: maxMatchesStr = '1'). This double-default means:

  • If a future caller forgets to set a default (or passes undefined explicitly), the threshold silently becomes 1 instead of triggering the config-validation error at line 57-59.
  • The validation gate that says "must be a positive integer" becomes unreachable for the undefined case — the most likely misconfiguration scenario.

The default policy belongs in one place. Since the threshold default is env-var-specific ("what does a missing GH_AW_SKIP_MAX_MATCHES mean?"), it should live at the caller's env-destructuring layer. Remove the default from the helper's options destructuring and let an undefined thresholdStr fall through to the isNaN check so it fails loudly:

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Triage — Run #7

Field Value
Category refactor
Risk 🟢 low
Score 44 / 100
Action 📋 batch_review
Batch pr-batch:js-refactor

Score breakdown: impact 18 + urgency 12 + quality 14

Extracts shared runSkipQueryGate helper from check_skip_if_match.cjs and check_skip_if_no_match.cjs, eliminating mirrored implementations. 4 files, +165/-95, includes test extension. Review alongside #43483 as part of the JS refactor batch.

Generated by 🔧 PR Triage Agent · 56.3 AIC · ⌖ 8.84 AIC · ⊞ 5.5K ·

@pelikhan

pelikhan commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

- Break single-line destructure to multi-line (Prettier at printWidth:240 normalizes it; semantically correct)
- Use workflowName in log line — eliminates dead validation
- Remove thresholdStr default from helper (callers own their defaults)
- Move runSkipQueryGate describe to top-level sibling of buildSearchQuery
- Remove duplicate loadModule inside runSkipQueryGate describe
- Add missing test cases: skipQuery/workflowName early-exit guards,
  success path (skip condition not met), API error path
- Assert writeDenialSummary side effects in skip-path test

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

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Done. Changes pushed in commit refactor: address review feedback on runSkipQueryGate helper:

helpers.cjs

  • workflowName now used in a core.info log after validation — eliminates the dead-validation concern
  • Removed thresholdStr = "1" default from the helper; ownership lives at each caller's env-destructuring layer
  • Destructure reformatted to multi-line (Prettier at printWidth: 240 normalises it back to one line per project convention)

test.cjs

  • Moved runSkipQueryGate describe to a top-level sibling of buildSearchQuery — fixes misleading nesting in CI test reports
  • Removed the duplicate loadModule declaration; shared via baseOptions spread pattern
  • Added 4 new tests: missing skipQuery early-exit, missing workflowName early-exit, success path (shouldSkip returns false → setOutput(..., "true")), and API error path (ERR_API prefix)
  • Added summary.addRaw / summary.write assertions to the skip-path test to verify writeDenialSummary side effects

All 16 unit tests pass locally (make test-unit ✅, make lint ✅).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[duplicate-code] Duplicate Code: Skip Gate Mirror Logic

3 participants