diff --git a/.github/workflows/ai-moderator.lock.yml b/.github/workflows/ai-moderator.lock.yml index a78ba3f00ff..bb494a8d969 100644 --- a/.github/workflows/ai-moderator.lock.yml +++ b/.github/workflows/ai-moderator.lock.yml @@ -120,7 +120,9 @@ env: jobs: activation: needs: pre_activation - if: needs.pre_activation.outputs.activated == 'true' + if: > + needs.pre_activation.outputs.activated == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.stack == null || + github.event.pull_request.stack.position == github.event.pull_request.stack.size) runs-on: ubuntu-slim permissions: actions: read @@ -1677,9 +1679,10 @@ jobs: pre_activation: if: > - (!(github.event_name == 'issue_comment') || !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) && + ((!(github.event_name == 'issue_comment') || !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) && (!(github.event_name == 'issues') || !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association)) && - (!(github.event_name == 'pull_request') || !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)) + (!(github.event_name == 'pull_request') || !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association))) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size) runs-on: ubuntu-slim permissions: actions: read diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index 48c936243df..baaf976267d 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -99,8 +99,9 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && (((github.event.pull_request.base.ref == github.event.repository.default_branch) && + needs.pre_activation.outputs.activated == 'true' && ((((github.event.pull_request.base.ref == github.event.repository.default_branch) && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id)) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'changeset' || github.event.label.name == 'smoke')) runs-on: ubuntu-slim @@ -1932,9 +1933,11 @@ jobs: pre_activation: if: > - ((github.event.pull_request.base.ref == github.event.repository.default_branch) && (github.event_name != 'pull_request' || + (((github.event.pull_request.base.ref == github.event.repository.default_branch) && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id)) && (github.event_name != 'pull_request' || - github.event.action != 'labeled' || github.event.label.name == 'changeset' || github.event.label.name == 'smoke') + github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && + (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'changeset' || + github.event.label.name == 'smoke') runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/design-decision-gate.lock.yml b/.github/workflows/design-decision-gate.lock.yml index 303d3cb050c..d9808b8d714 100644 --- a/.github/workflows/design-decision-gate.lock.yml +++ b/.github/workflows/design-decision-gate.lock.yml @@ -112,7 +112,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'implementation')) runs-on: ubuntu-slim permissions: @@ -1986,7 +1987,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'implementation') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/firewall-escape.lock.yml b/.github/workflows/firewall-escape.lock.yml index f6b93ce5e8c..4dda5753d77 100644 --- a/.github/workflows/firewall-escape.lock.yml +++ b/.github/workflows/firewall-escape.lock.yml @@ -95,7 +95,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'firewall-escape-test')) runs-on: ubuntu-slim permissions: @@ -1707,7 +1708,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'firewall-escape-test') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/impeccable-skills-reviewer.lock.yml b/.github/workflows/impeccable-skills-reviewer.lock.yml index d98ef72a37d..b265d97758c 100644 --- a/.github/workflows/impeccable-skills-reviewer.lock.yml +++ b/.github/workflows/impeccable-skills-reviewer.lock.yml @@ -104,7 +104,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) + needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) runs-on: ubuntu-slim permissions: actions: read @@ -1647,7 +1648,9 @@ jobs: bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json pre_activation: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id + if: > + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/mattpocock-skills-reviewer.lock.yml b/.github/workflows/mattpocock-skills-reviewer.lock.yml index 1b7aef1f13a..f18abadb393 100644 --- a/.github/workflows/mattpocock-skills-reviewer.lock.yml +++ b/.github/workflows/mattpocock-skills-reviewer.lock.yml @@ -102,7 +102,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) + needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) runs-on: ubuntu-slim permissions: actions: read @@ -1819,7 +1820,9 @@ jobs: bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json pre_activation: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id + if: > + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/pr-code-quality-reviewer.lock.yml b/.github/workflows/pr-code-quality-reviewer.lock.yml index bb4df3a2340..5eb46844b75 100644 --- a/.github/workflows/pr-code-quality-reviewer.lock.yml +++ b/.github/workflows/pr-code-quality-reviewer.lock.yml @@ -103,7 +103,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) + needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) runs-on: ubuntu-slim permissions: actions: read @@ -1958,7 +1959,9 @@ jobs: if-no-files-found: ignore pre_activation: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id + if: > + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/pr-description-caveman.lock.yml b/.github/workflows/pr-description-caveman.lock.yml index a9645ee8f9c..23b82279bc3 100644 --- a/.github/workflows/pr-description-caveman.lock.yml +++ b/.github/workflows/pr-description-caveman.lock.yml @@ -71,9 +71,10 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event.pull_request.merged == true && !startsWith(github.event.pull_request.head.ref, 'signed/jsweep/') && + needs.pre_activation.outputs.activated == 'true' && (((github.event.pull_request.merged == true && !startsWith(github.event.pull_request.head.ref, 'signed/jsweep/') && !startsWith(github.event.pull_request.head.ref, 'copilot/')) && (github.event_name != 'pull_request' || - github.event.pull_request.head.repo.id == github.repository_id)) + github.event.pull_request.head.repo.id == github.repository_id)) && (github.event_name != 'pull_request' || + github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) runs-on: ubuntu-slim permissions: actions: read @@ -1546,9 +1547,10 @@ jobs: pre_activation: if: > - (github.event.pull_request.merged == true && !startsWith(github.event.pull_request.head.ref, 'signed/jsweep/') && + ((github.event.pull_request.merged == true && !startsWith(github.event.pull_request.head.ref, 'signed/jsweep/') && !startsWith(github.event.pull_request.head.ref, 'copilot/')) && (github.event_name != 'pull_request' || - github.event.pull_request.head.repo.id == github.repository_id) + github.event.pull_request.head.repo.id == github.repository_id)) && (github.event_name != 'pull_request' || + github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/refiner.lock.yml b/.github/workflows/refiner.lock.yml index fac8fea8fd4..57e1a00ff86 100644 --- a/.github/workflows/refiner.lock.yml +++ b/.github/workflows/refiner.lock.yml @@ -102,7 +102,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'refine')) runs-on: ubuntu-slim permissions: @@ -1930,7 +1931,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'refine') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-agent-all-merged.lock.yml b/.github/workflows/smoke-agent-all-merged.lock.yml index ff7ff02b847..28dd26ecfdd 100644 --- a/.github/workflows/smoke-agent-all-merged.lock.yml +++ b/.github/workflows/smoke-agent-all-merged.lock.yml @@ -92,7 +92,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'metal')) runs-on: ubuntu-slim permissions: @@ -1689,7 +1690,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'metal') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-agent-all-none.lock.yml b/.github/workflows/smoke-agent-all-none.lock.yml index b60afd17b98..202e225afbb 100644 --- a/.github/workflows/smoke-agent-all-none.lock.yml +++ b/.github/workflows/smoke-agent-all-none.lock.yml @@ -92,7 +92,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'metal')) runs-on: ubuntu-slim permissions: @@ -1689,7 +1690,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'metal') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-agent-public-approved.lock.yml b/.github/workflows/smoke-agent-public-approved.lock.yml index e6eef8459d8..1cc28f795b3 100644 --- a/.github/workflows/smoke-agent-public-approved.lock.yml +++ b/.github/workflows/smoke-agent-public-approved.lock.yml @@ -94,7 +94,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'metal')) runs-on: ubuntu-slim permissions: @@ -1741,7 +1742,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'metal') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-agent-public-none.lock.yml b/.github/workflows/smoke-agent-public-none.lock.yml index b6454738d9a..d7507768b18 100644 --- a/.github/workflows/smoke-agent-public-none.lock.yml +++ b/.github/workflows/smoke-agent-public-none.lock.yml @@ -92,7 +92,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'metal')) runs-on: ubuntu-slim permissions: @@ -1689,7 +1690,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'metal') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-agent-scoped-approved.lock.yml b/.github/workflows/smoke-agent-scoped-approved.lock.yml index 8592ec80158..59b7f86856b 100644 --- a/.github/workflows/smoke-agent-scoped-approved.lock.yml +++ b/.github/workflows/smoke-agent-scoped-approved.lock.yml @@ -93,7 +93,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'metal')) runs-on: ubuntu-slim permissions: @@ -1696,7 +1697,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'metal') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-antigravity.lock.yml b/.github/workflows/smoke-antigravity.lock.yml index 8390c31ff7a..13122fe85b0 100644 --- a/.github/workflows/smoke-antigravity.lock.yml +++ b/.github/workflows/smoke-antigravity.lock.yml @@ -95,7 +95,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke')) runs-on: ubuntu-slim permissions: @@ -1953,7 +1954,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-call-workflow.lock.yml b/.github/workflows/smoke-call-workflow.lock.yml index 46bc4ad500c..2425c6a4a16 100644 --- a/.github/workflows/smoke-call-workflow.lock.yml +++ b/.github/workflows/smoke-call-workflow.lock.yml @@ -93,7 +93,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'water')) runs-on: ubuntu-slim permissions: @@ -1737,7 +1738,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'water') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-ci.lock.yml b/.github/workflows/smoke-ci.lock.yml index 86a690aae6b..184b3333e53 100644 --- a/.github/workflows/smoke-ci.lock.yml +++ b/.github/workflows/smoke-ci.lock.yml @@ -109,7 +109,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) + needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) runs-on: ubuntu-slim permissions: actions: read @@ -1583,7 +1584,9 @@ jobs: await main(); pre_activation: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id + if: > + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index b845eb40258..182da3fa1fd 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -105,7 +105,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke')) runs-on: ubuntu-slim permissions: @@ -2583,7 +2584,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 28c2a90e863..da0743e95c3 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -105,7 +105,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke')) runs-on: ubuntu-slim permissions: @@ -2196,7 +2197,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-copilot-arm.lock.yml b/.github/workflows/smoke-copilot-arm.lock.yml index 3488db02de7..0aef13f9b11 100644 --- a/.github/workflows/smoke-copilot-arm.lock.yml +++ b/.github/workflows/smoke-copilot-arm.lock.yml @@ -102,7 +102,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'water')) runs-on: ubuntu-slim permissions: @@ -2614,7 +2615,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'water') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-create-cross-repo-pr.lock.yml b/.github/workflows/smoke-create-cross-repo-pr.lock.yml index 0b2df121a32..5922849e255 100644 --- a/.github/workflows/smoke-create-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-create-cross-repo-pr.lock.yml @@ -93,7 +93,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke-create-cross-repo-pr')) runs-on: ubuntu-slim permissions: @@ -1743,7 +1744,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke-create-cross-repo-pr') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index d03d32ba5ca..6099a44b9f2 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -97,7 +97,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke')) runs-on: ubuntu-slim permissions: @@ -2034,7 +2035,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-multi-pr.lock.yml b/.github/workflows/smoke-multi-pr.lock.yml index 5caa9e1417b..a72121db37a 100644 --- a/.github/workflows/smoke-multi-pr.lock.yml +++ b/.github/workflows/smoke-multi-pr.lock.yml @@ -92,7 +92,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke-multi-pr')) runs-on: ubuntu-slim permissions: @@ -1695,7 +1696,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke-multi-pr') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index af43c68a8b2..a28457c2afc 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -95,7 +95,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'water')) runs-on: ubuntu-slim permissions: @@ -1751,7 +1752,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'water') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-pi.lock.yml b/.github/workflows/smoke-pi.lock.yml index 5d19c1b412c..3be50275bac 100644 --- a/.github/workflows/smoke-pi.lock.yml +++ b/.github/workflows/smoke-pi.lock.yml @@ -95,7 +95,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke')) runs-on: ubuntu-slim permissions: @@ -1695,7 +1696,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-project.lock.yml b/.github/workflows/smoke-project.lock.yml index e5a4924cd31..0b5fca974e2 100644 --- a/.github/workflows/smoke-project.lock.yml +++ b/.github/workflows/smoke-project.lock.yml @@ -94,7 +94,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'water')) runs-on: ubuntu-slim permissions: @@ -2175,7 +2176,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'water') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-temporary-id.lock.yml b/.github/workflows/smoke-temporary-id.lock.yml index 5b9a6bed59e..64da28203b7 100644 --- a/.github/workflows/smoke-temporary-id.lock.yml +++ b/.github/workflows/smoke-temporary-id.lock.yml @@ -92,7 +92,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'water')) runs-on: ubuntu-slim permissions: @@ -2023,7 +2024,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'water') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-test-tools.lock.yml b/.github/workflows/smoke-test-tools.lock.yml index 1076d897275..be8fa19c52a 100644 --- a/.github/workflows/smoke-test-tools.lock.yml +++ b/.github/workflows/smoke-test-tools.lock.yml @@ -96,7 +96,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke')) runs-on: ubuntu-slim permissions: @@ -1718,7 +1719,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/smoke-update-cross-repo-pr.lock.yml b/.github/workflows/smoke-update-cross-repo-pr.lock.yml index 8b43c59cc3f..7db66ec3f81 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-update-cross-repo-pr.lock.yml @@ -93,7 +93,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + needs.pre_activation.outputs.activated == 'true' && (((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke-update-cross-repo-pr')) runs-on: ubuntu-slim permissions: @@ -1777,7 +1778,8 @@ jobs: pre_activation: if: > - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) && (github.event_name != 'pull_request' || github.event.action != 'labeled' || github.event.label.name == 'smoke-update-cross-repo-pr') runs-on: ubuntu-slim permissions: diff --git a/.github/workflows/test-quality-sentinel.lock.yml b/.github/workflows/test-quality-sentinel.lock.yml index d771baa6196..c8d189c216a 100644 --- a/.github/workflows/test-quality-sentinel.lock.yml +++ b/.github/workflows/test-quality-sentinel.lock.yml @@ -93,7 +93,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) + needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) runs-on: ubuntu-slim permissions: actions: read @@ -1940,7 +1941,9 @@ jobs: if-no-files-found: ignore pre_activation: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id + if: > + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/visual-regression-checker.lock.yml b/.github/workflows/visual-regression-checker.lock.yml index 2e79635de3a..c3797cef771 100644 --- a/.github/workflows/visual-regression-checker.lock.yml +++ b/.github/workflows/visual-regression-checker.lock.yml @@ -91,7 +91,8 @@ jobs: activation: needs: pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) + needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) runs-on: ubuntu-slim permissions: actions: read @@ -1639,7 +1640,9 @@ jobs: } pre_activation: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id + if: > + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size) runs-on: ubuntu-slim permissions: contents: read diff --git a/docs/src/content/docs/reference/triggers.md b/docs/src/content/docs/reference/triggers.md index deda316d31a..2defda6d0a4 100644 --- a/docs/src/content/docs/reference/triggers.md +++ b/docs/src/content/docs/reference/triggers.md @@ -163,6 +163,39 @@ on: Use `["owner/repo"]` for a specific repository, `["owner/*"]` for an entire org/user, or `["*"]` to allow all forks (use with caution). Omit `forks:` for the default behavior (same-repository PRs only). The compiler uses repository ID comparison so fork detection is unaffected by repository renames. +#### Stacked PR Filtering (`max-stack:`) + +When using stacked pull requests (a chain of PRs each targeting the previous one), every PR in the stack normally triggers workflows. This multiplies CI cost for identical changes being reviewed at multiple layers. The `max-stack:` field lets you limit which stack layers run the workflow. + +By default (`max-stack: 1`), workflows run only on the **top-most PR** in the stack — the one currently under review. Lower-stack PRs are skipped automatically. + +```yaml wrap +on: + pull_request: + types: [opened, synchronize] + max-stack: 1 # default: run only on the top/latest PR in a stack +``` + +To run on the **top N** layers (for example, if you review multiple interdependent PRs at once): + +```yaml wrap +on: + pull_request: + types: [opened, synchronize] + max-stack: 2 # run on the top 2 PRs in a stack +``` + +To **disable** stack protection and run on every PR in a stack: + +```yaml wrap +on: + pull_request: + types: [opened, synchronize] + max-stack: -1 # run on all pull requests regardless of stack position +``` + +Non-stacked PRs and non-`pull_request` events are unaffected by this setting. + ### Comment Triggers The triggers `issue_comment:`, `pull_request_review_comment:`, and `discussion_comment:` activate workflows when comments are created or edited. diff --git a/pkg/parser/schema_location_test.go b/pkg/parser/schema_location_test.go index 8acfddddccc..4489ce561ba 100644 --- a/pkg/parser/schema_location_test.go +++ b/pkg/parser/schema_location_test.go @@ -646,3 +646,67 @@ func TestValidateIncludedFileFrontmatterWithSchemaAndLocation_SkipsCustomAgentFi } } } + +func TestValidateMainWorkflowFrontmatterWithSchemaAndLocation_MaxStack(t *testing.T) { + tests := []struct { + name string + maxStack any + wantErr bool + errContains string + }{ + { + name: "max-stack: 1 is valid (default)", + maxStack: 1, + wantErr: false, + }, + { + name: "max-stack: 2 is valid", + maxStack: 2, + wantErr: false, + }, + { + name: "max-stack: -1 is valid (disable stack protection)", + maxStack: -1, + wantErr: false, + }, + { + name: "max-stack: 0 is rejected", + maxStack: 0, + wantErr: true, + errContains: "max-stack", + }, + { + name: "max-stack: -2 is rejected", + maxStack: -2, + wantErr: true, + errContains: "max-stack", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + frontmatter := map[string]any{ + "on": map[string]any{ + "pull_request": map[string]any{ + "types": []any{"opened"}, + "max-stack": tt.maxStack, + }, + }, + } + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/test/workflow.md") + if tt.wantErr && err == nil { + t.Errorf("expected validation error for max-stack: %v, got nil", tt.maxStack) + return + } + if !tt.wantErr && err != nil { + t.Errorf("unexpected validation error for max-stack: %v: %v", tt.maxStack, err) + return + } + if tt.wantErr && err != nil && tt.errContains != "" { + if !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("expected error containing %q, got: %v", tt.errContains, err) + } + } + }) + } +} diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 32c258c919c..d3d24ea83d9 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -944,6 +944,21 @@ "type": "boolean", "description": "Filter by draft pull request state. Set to false to exclude draft PRs, true to include only drafts, or omit to include both" }, + "max-stack": { + "anyOf": [ + { + "type": "integer", + "const": -1, + "description": "Disable stack protection; run on every pull request in the stack." + }, + { + "type": "integer", + "minimum": 1, + "description": "Run only on the top N pull requests in the stack. Default is 1 (only the latest/top pull request)." + } + ], + "description": "Maximum number of top stack layers to run on for stacked pull requests. Default is 1 (only the latest/top pull request in the stack). Set to -1 to disable stack protection and run on every pull request in the stack. Value 0 is not allowed." + }, "forks": { "oneOf": [ { diff --git a/pkg/workflow/compiler_orchestrator_workflow.go b/pkg/workflow/compiler_orchestrator_workflow.go index 341a7418173..3f206ec5f12 100644 --- a/pkg/workflow/compiler_orchestrator_workflow.go +++ b/pkg/workflow/compiler_orchestrator_workflow.go @@ -667,6 +667,9 @@ func (c *Compiler) processOnSectionAndFilters( // Apply pull request fork filter if specified c.applyPullRequestForkFilter(workflowData, frontmatter) + // Apply pull request stack filter (default: latest stacked PR only) + c.applyPullRequestStackFilter(workflowData, frontmatter) + // Apply label filter if specified c.applyLabelFilter(workflowData, frontmatter) diff --git a/pkg/workflow/filters.go b/pkg/workflow/filters.go index cda03b2125e..02b6f1ebfa5 100644 --- a/pkg/workflow/filters.go +++ b/pkg/workflow/filters.go @@ -1,7 +1,9 @@ package workflow import ( + "fmt" "slices" + "strings" "github.com/github/gh-aw/pkg/logger" ) @@ -192,6 +194,123 @@ func (c *Compiler) applyPullRequestForkFilter(data *WorkflowData, frontmatter ma data.If = RenderCondition(conditionTree) } +// applyPullRequestStackFilter applies stacked pull request protection. +// Default behavior: run only for the latest PR in a stack (max-stack = 1). +// Set on.pull_request.max-stack: -1 to disable this protection. +func (c *Compiler) applyPullRequestStackFilter(data *WorkflowData, frontmatter map[string]any) { + filtersLog.Print("Applying pull request stack filter") + + onValue, hasOn := frontmatter["on"] + if !hasOn || !hasPullRequestTrigger(onValue) { + return + } + + maxStack := 1 + if configuredMaxStack, ok := extractPullRequestMaxStack(onValue); ok { + maxStack = configuredMaxStack + } + + if maxStack == -1 { + filtersLog.Print("Pull request stack filter disabled via max-stack: -1") + return + } + + if maxStack == 1 { + // For max-stack: 1 (the default), use a job-level if: condition with the supported + // equality operator. This gates the entire job (shows as "skipped") for non-top PRs. + // GitHub Actions expressions do not support arithmetic (+, -, etc.), so we use + // position == size to check that this PR is at the top of the stack. + stackCondition := "github.event_name != 'pull_request' || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size" + + existingCondition := data.If + conditionTree := BuildConditionTree(existingCondition, stackCondition) + data.If = RenderCondition(conditionTree) + } else { + // For max-stack: N > 1, GitHub Actions expressions do not support arithmetic + // operators (+, -, etc.), so we inject a PreStep that uses bash arithmetic instead. + // The step exits 1 if this PR is not in the top N layers, stopping all subsequent + // default-condition steps from running (they use if: success() by default). + stackGateStep := fmt.Sprintf( + "- name: Stack position gate (max-stack: %d)\n"+ + " if: github.event_name == 'pull_request' && github.event.pull_request.stack != null\n"+ + " env:\n"+ + " STACK_POSITION: ${{ github.event.pull_request.stack.position }}\n"+ + " STACK_SIZE: ${{ github.event.pull_request.stack.size }}\n"+ + " run: |\n"+ + " max_stack=%d\n"+ + " if (( STACK_POSITION + max_stack <= STACK_SIZE )); then\n"+ + " printf '## Stack gate\\n\\nRun skipped: stack position %%s is not in the top %%s of %%s.\\n' \\\n"+ + " \"$STACK_POSITION\" \"$max_stack\" \"$STACK_SIZE\" >> \"$GITHUB_STEP_SUMMARY\"\n"+ + " exit 1\n"+ + " fi\n", + maxStack, maxStack, + ) + + if data.PreSteps == "" { + data.PreSteps = "pre-steps:\n" + stackGateStep + } else { + data.PreSteps = strings.TrimRight(data.PreSteps, "\n") + "\n" + stackGateStep + } + } +} + +func hasPullRequestTrigger(onValue any) bool { + switch on := onValue.(type) { + case string: + return on == "pull_request" + case []any: + for _, item := range on { + if item == "pull_request" { + return true + } + if eventMap, ok := item.(map[string]any); ok { + if _, exists := eventMap["pull_request"]; exists { + return true + } + } + } + case map[string]any: + _, exists := on["pull_request"] + return exists + } + return false +} + +func extractPullRequestMaxStack(onValue any) (int, bool) { + onMap, ok := onValue.(map[string]any) + if !ok { + return 0, false + } + + prValue, hasPR := onMap["pull_request"] + if !hasPR { + return 0, false + } + + prMap, ok := prValue.(map[string]any) + if !ok { + return 0, false + } + + maxStackValue, hasMaxStack := prMap["max-stack"] + if !hasMaxStack { + return 0, false + } + + switch v := maxStackValue.(type) { + case int: + return v, true + case int64: + return int(v), true + case float64: + if v == float64(int(v)) { + return int(v), true + } + } + + return 0, false +} + // applyLabelFilter applies label name filter conditions for labeled/unlabeled triggers // Supports "names: []string" to filter which label changes trigger the workflow func (c *Compiler) applyLabelFilter(data *WorkflowData, frontmatter map[string]any) { diff --git a/pkg/workflow/filters_stack_integration_test.go b/pkg/workflow/filters_stack_integration_test.go new file mode 100644 index 00000000000..da9dd1514da --- /dev/null +++ b/pkg/workflow/filters_stack_integration_test.go @@ -0,0 +1,149 @@ +//go:build integration + +package workflow + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/github/gh-aw/pkg/stringutil" + "github.com/stretchr/testify/require" +) + +// TestStackFilterJobIfContainsNoArithmetic is an actionlint-backed regression test that +// verifies the compiled workflow's job-level if: condition does not contain arithmetic +// operators (+, -, *, /) which are not supported by GitHub Actions expressions. +// Previously, max-stack generated "stack.position + N > stack.size" which actionlint +// (correctly) rejected; now it uses equality or a PreStep for arithmetic. +func TestStackFilterJobIfContainsNoArithmetic(t *testing.T) { + tests := []struct { + name string + workflowBody string + }{ + { + name: "default max-stack (1) uses equality not arithmetic", + workflowBody: `--- +on: + pull_request: + types: [opened, synchronize] +permissions: + contents: read +--- + +Test workflow. +`, + }, + { + name: "explicit max-stack: 1 uses equality not arithmetic", + workflowBody: `--- +on: + pull_request: + types: [opened] + max-stack: 1 +permissions: + contents: read +--- + +Test workflow. +`, + }, + { + name: "max-stack: -1 does not add stack condition", + workflowBody: `--- +on: + pull_request: + types: [opened] + max-stack: -1 +permissions: + contents: read +--- + +Test workflow. +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + workflowPath := filepath.Join(tempDir, "test-workflow.md") + err := os.WriteFile(workflowPath, []byte(tt.workflowBody), 0644) + require.NoError(t, err) + + compiler := NewCompiler() + err = compiler.CompileWorkflow(workflowPath) + require.NoError(t, err) + + lockPath := stringutil.MarkdownToLockFile(workflowPath) + lockContent, err := os.ReadFile(lockPath) + require.NoError(t, err) + + lockStr := string(lockContent) + + // The compiled YAML job-level if: must not contain arithmetic operators. + // GitHub Actions expression syntax does not support +, -, *, / operators. + // Actionlint rejects any if: condition containing these. + for _, line := range strings.Split(lockStr, "\n") { + trimmed := strings.TrimSpace(line) + // Only check lines that are job-level "if:" conditions (inside jobs block) + if strings.HasPrefix(trimmed, "if: ") { + ifValue := strings.TrimPrefix(trimmed, "if: ") + require.NotContains(t, ifValue, "stack.position +", + "job-level if: must not contain arithmetic (+); actionlint rejects this.\n"+ + "Line: %s\nFull lock file:\n%s", line, lockStr) + require.NotContains(t, ifValue, "stack.position -", + "job-level if: must not contain arithmetic (-); actionlint rejects this") + } + } + }) + } +} + +// TestStackFilterMaxStack2UsesPreStep verifies that max-stack: 2 (N>1) +// generates a PreStep with bash arithmetic rather than an invalid +// arithmetic expression in the job-level if: condition. +func TestStackFilterMaxStack2UsesPreStep(t *testing.T) { + workflowBody := `--- +on: + pull_request: + types: [opened] + max-stack: 2 +permissions: + contents: read +--- + +Test workflow. +` + tempDir := t.TempDir() + workflowPath := filepath.Join(tempDir, "test-workflow.md") + err := os.WriteFile(workflowPath, []byte(workflowBody), 0644) + require.NoError(t, err) + + compiler := NewCompiler() + err = compiler.CompileWorkflow(workflowPath) + require.NoError(t, err) + + lockPath := stringutil.MarkdownToLockFile(workflowPath) + lockContent, err := os.ReadFile(lockPath) + require.NoError(t, err) + + lockStr := string(lockContent) + + // The PreStep with bash arithmetic must be present in the compiled YAML. + require.Contains(t, lockStr, "Stack position gate (max-stack: 2)", + "max-stack: 2 should generate a stack gate PreStep in the compiled YAML") + require.Contains(t, lockStr, "STACK_POSITION", + "stack gate step should use STACK_POSITION env var") + + // The job-level if: condition must NOT contain arithmetic operators. + for _, line := range strings.Split(lockStr, "\n") { + trimmed := strings.TrimSpace(line) + if strings.HasPrefix(trimmed, "if: ") { + ifValue := strings.TrimPrefix(trimmed, "if: ") + require.NotContains(t, ifValue, "stack.position +", + "job-level if: must not contain arithmetic operators for max-stack: 2.\nLine: %s", line) + } + } +} diff --git a/pkg/workflow/filters_stack_test.go b/pkg/workflow/filters_stack_test.go new file mode 100644 index 00000000000..89d214fcb8d --- /dev/null +++ b/pkg/workflow/filters_stack_test.go @@ -0,0 +1,123 @@ +package workflow + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestApplyPullRequestStackFilter_DefaultMaxStack(t *testing.T) { + compiler := NewCompiler() + workflowData := &WorkflowData{} + frontmatter := map[string]any{ + "on": map[string]any{ + "pull_request": map[string]any{ + "types": []any{"opened"}, + }, + }, + } + + compiler.applyPullRequestStackFilter(workflowData, frontmatter) + + assert.Contains(t, workflowData.If, "github.event.pull_request.stack == null") + // max-stack: 1 (default) uses equality (== size) rather than arithmetic (+ N > size) + // GitHub Actions expressions do not support arithmetic operators. + assert.Contains(t, workflowData.If, "github.event.pull_request.stack.position == github.event.pull_request.stack.size") + assert.NotContains(t, workflowData.If, "+", "job-level if must not contain arithmetic operators") +} + +func TestApplyPullRequestStackFilter_ConfiguredMaxStack(t *testing.T) { + compiler := NewCompiler() + workflowData := &WorkflowData{} + frontmatter := map[string]any{ + "on": map[string]any{ + "pull_request": map[string]any{ + "types": []any{"opened"}, + "max-stack": 3, + }, + }, + } + + compiler.applyPullRequestStackFilter(workflowData, frontmatter) + + // For max-stack: N > 1, the arithmetic check must be in a PreStep, not in job-level if: + assert.Empty(t, workflowData.If, "job-level if should not contain arithmetic for max-stack > 1") + assert.Contains(t, workflowData.PreSteps, "Stack position gate (max-stack: 3)") + assert.Contains(t, workflowData.PreSteps, "max_stack=3") + assert.Contains(t, workflowData.PreSteps, "STACK_POSITION + max_stack <= STACK_SIZE") + assert.NotContains(t, workflowData.If, "+", "job-level if must not contain arithmetic operators") +} + +func TestApplyPullRequestStackFilter_Disabled(t *testing.T) { + compiler := NewCompiler() + workflowData := &WorkflowData{If: "github.actor != 'dependabot[bot]'"} + frontmatter := map[string]any{ + "on": map[string]any{ + "pull_request": map[string]any{ + "types": []any{"opened"}, + "max-stack": -1, + }, + }, + } + + compiler.applyPullRequestStackFilter(workflowData, frontmatter) + + assert.Equal(t, "github.actor != 'dependabot[bot]'", workflowData.If) + assert.Empty(t, workflowData.PreSteps) +} + +func TestApplyPullRequestStackFilter_SimplePullRequestTrigger(t *testing.T) { + compiler := NewCompiler() + workflowData := &WorkflowData{} + frontmatter := map[string]any{ + "on": "pull_request", + } + + compiler.applyPullRequestStackFilter(workflowData, frontmatter) + + // String trigger form — max-stack defaults to 1, so equality expression is used + assert.Contains(t, workflowData.If, "github.event.pull_request.stack.position == github.event.pull_request.stack.size") + assert.NotContains(t, workflowData.If, "+", "job-level if must not contain arithmetic operators") +} + +func TestApplyPullRequestStackFilter_NoPullRequestTrigger(t *testing.T) { + compiler := NewCompiler() + workflowData := &WorkflowData{If: "github.actor != 'dependabot[bot]'"} + frontmatter := map[string]any{ + "on": map[string]any{ + "push": map[string]any{ + "branches": []any{"main"}, + }, + }, + } + + compiler.applyPullRequestStackFilter(workflowData, frontmatter) + + assert.Equal(t, "github.actor != 'dependabot[bot]'", workflowData.If) + assert.Empty(t, workflowData.PreSteps) +} + +func TestApplyPullRequestStackFilter_ExistingPreStepsAppended(t *testing.T) { + compiler := NewCompiler() + workflowData := &WorkflowData{ + PreSteps: "pre-steps:\n- name: existing-step\n run: |\n echo hello\n", + } + frontmatter := map[string]any{ + "on": map[string]any{ + "pull_request": map[string]any{ + "types": []any{"opened"}, + "max-stack": 2, + }, + }, + } + + compiler.applyPullRequestStackFilter(workflowData, frontmatter) + + assert.Contains(t, workflowData.PreSteps, "existing-step") + assert.Contains(t, workflowData.PreSteps, "Stack position gate (max-stack: 2)") + // Both steps should be present + idx1 := strings.Index(workflowData.PreSteps, "existing-step") + idx2 := strings.Index(workflowData.PreSteps, "Stack position gate") + assert.Greater(t, idx2, idx1, "stack gate step should appear after existing pre-steps") +} diff --git a/pkg/workflow/frontmatter_on_section_cleanup.go b/pkg/workflow/frontmatter_on_section_cleanup.go index a5183b59c59..27c62fef7d4 100644 --- a/pkg/workflow/frontmatter_on_section_cleanup.go +++ b/pkg/workflow/frontmatter_on_section_cleanup.go @@ -7,7 +7,7 @@ import ( "github.com/github/gh-aw/pkg/setutil" ) -// commentOutProcessedFieldsInOnSection comments out draft, fork, forks, names, labels, manual-approval, stop-after, skip-if-match, skip-if-no-match, skip-roles, reaction, lock-for-agent, steps, permissions, needs, restore-memory, and stale-check fields in the on section +// commentOutProcessedFieldsInOnSection comments out draft, max-stack, fork, forks, names, labels, manual-approval, stop-after, skip-if-match, skip-if-no-match, skip-roles, reaction, lock-for-agent, steps, permissions, needs, restore-memory, and stale-check fields in the on section // These fields are processed separately and should be commented for documentation // Exception: names fields in sections with __gh_aw_native_label_filter__ marker in frontmatter are NOT commented out func (c *Compiler) commentOutProcessedFieldsInOnSection(yamlStr string, frontmatter map[string]any) string { @@ -513,6 +513,8 @@ func (s *onSectionCleanupState) commentPullRequestAndTriggerField(info onSection switch { case s.inPullRequest && strings.Contains(info.trimmed, "draft:"): return true, " # Draft filtering applied via job conditions" + case s.inPullRequest && strings.HasPrefix(info.trimmed, "max-stack:"): + return true, " # Stack filtering applied via job conditions" case s.inPullRequest && strings.HasPrefix(info.trimmed, "forks:"): return true, " # Fork filtering applied via job conditions" case s.inForksArray && strings.HasPrefix(info.trimmed, "-"):