Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@ Resolves: #NNNNN <!-- Replace `NNNNN` with a GitHub issue number, or a direct li

## Build configuration
<!--
Comment `/build` on this PR to dispatch consumer-app builds against this
framework PR. Edit the lines below to override defaults; remove the
section entirely to use defaults.
Consumer-app builds run automatically for every PR. Edit the lines below to
override defaults; remove the section entirely to use defaults.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Format: {owner}/{repo}/{branch} (any public fork works).

Available platforms (space-separated):
audacity: linux_x64 macos windows_x64
musescore: linux_x64 linux_arm64 macos windows_x64 windows_portable
musescore: linux_x64 macos windows_x64
-->
audacity: audacity/audacity/master
audacity platforms: linux_x64
Expand Down
92 changes: 14 additions & 78 deletions .github/workflows/pr_dispatch.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
name: pr_dispatch
name: Applications Build

on:
issue_comment:
types: [created]
pull_request:
types: [opened, reopened, synchronize, edited]
Comment on lines 3 to +5

@coderabbitai coderabbitai Bot Jun 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a trust gate for automatic PR-triggered builds.

Line 5 now runs this workflow on every PR update (including edited) without any trust check. That allows untrusted/fork PRs to automatically dispatch heavyweight builds, which can exhaust CI capacity and starve trusted builds.

Suggested hardening
 jobs:
   parse:
+    if: |
+      github.event.pull_request.head.repo.fork == false ||
+      contains(github.event.pull_request.labels.*.name, 'safe-to-test')
     name: Configure
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr_dispatch.yml around lines 3 - 5, The pull_request
trigger in the workflow does not include a trust gate, allowing untrusted forks
to automatically dispatch builds. Add a conditional check (typically using `if:
github.event.pull_request.head.repo.full_name == github.repository`) at the job
level to ensure that only PRs from the main repository trigger the heavyweight
build dispatch, preventing fork PRs from consuming CI capacity.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@igorkorsukov what do you think? I guess we are good, and if it is abused, we can tighten it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


permissions:
pull-requests: write
contents: read
statuses: write

concurrency:
group: pr-dispatch-${{ github.event.issue.number }}
group: pr-dispatch-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
parse:
if: >-
github.event.issue.pull_request != null
&& startsWith(github.event.comment.body, '/build')
&& contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
name: Configure
Comment on lines 15 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Skip edited runs that don't change the PR body.

parse only consumes pull_request.body, so title-only edits now cancel the in-flight build and dispatch the same matrix again under the same concurrency key. Add a job-level guard so edited only runs when the body changed.

Also applies to: 29-35

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr_dispatch.yml around lines 15 - 16, The parse job in
pr_dispatch.yml is triggering on edited events even when only the PR title
changes, which re-dispatches the same matrix under the same concurrency key. Add
a job-level condition on the parse job so it only runs for edited actions when
the pull_request body actually changed, keeping the existing
pull_request.body-based parsing behavior intact.

runs-on: ubuntu-latest
outputs:
head_sha: ${{ steps.pr.outputs.head_sha }}
Expand All @@ -31,19 +26,10 @@ jobs:
mu_branch: ${{ steps.cfg.outputs.mu_branch }}
mu_platforms: ${{ steps.cfg.outputs.mu_platforms }}
steps:
- name: React with eyes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api -X POST \
"repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \
-f content=eyes
- name: Fetch PR
id: pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pr_json=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}")
pr_json=$(jq -c '.pull_request' "$GITHUB_EVENT_PATH")
head_sha=$(echo "$pr_json" | jq -r .head.sha)
echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT"
echo "$pr_json" | jq -r .body > /tmp/pr_body.txt
Expand Down Expand Up @@ -84,28 +70,9 @@ jobs:
echo "mu_branch=$mu_branch"
echo "mu_platforms=$mu_platforms"
} >> "$GITHUB_OUTPUT"
- name: Post pending statuses + run-link comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SHA: ${{ steps.pr.outputs.head_sha }}
AU_PLATFORMS: ${{ steps.cfg.outputs.au_platforms }}
MU_PLATFORMS: ${{ steps.cfg.outputs.mu_platforms }}
run: |
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
post_pending() {
gh api -X POST "repos/${{ github.repository }}/statuses/$SHA" \
-f state=pending -f context="$1" -f target_url="$RUN_URL" -f description="dispatched"
}
[[ "$AU_PLATFORMS" == *linux_x64* ]] && post_pending "build / Audacity Linux"
[[ "$AU_PLATFORMS" == *macos* ]] && post_pending "build / Audacity macOS"
[[ "$AU_PLATFORMS" == *windows_x64* ]] && post_pending "build / Audacity Windows"
[[ "$MU_PLATFORMS" =~ linux_(x64|arm64) ]] && post_pending "build / MuseScore Linux"
[[ "$MU_PLATFORMS" == *macos* ]] && post_pending "build / MuseScore macOS"
[[ "$MU_PLATFORMS" =~ windows_(x64|portable) ]] && post_pending "build / MuseScore Windows"
gh pr comment "${{ github.event.issue.number }}" --repo "${{ github.repository }}" \
--body "Build dispatched: $RUN_URL"

build_au_linux:
name: Audacity Linux
needs: parse
if: contains(needs.parse.outputs.au_platforms, 'linux_x64')
uses: audacity/audacity/.github/workflows/au4_build_linux.yml@master
Expand All @@ -116,6 +83,7 @@ jobs:
framework_ref: ${{ needs.parse.outputs.head_sha }}

build_au_macos:
name: Audacity macOS
needs: parse
if: contains(needs.parse.outputs.au_platforms, 'macos')
uses: audacity/audacity/.github/workflows/au4_build_macos.yml@master
Expand All @@ -126,6 +94,7 @@ jobs:
framework_ref: ${{ needs.parse.outputs.head_sha }}

build_au_windows:
name: Audacity Windows
needs: parse
if: contains(needs.parse.outputs.au_platforms, 'windows_x64')
uses: audacity/audacity/.github/workflows/au4_build_windows.yml@master
Expand All @@ -136,18 +105,20 @@ jobs:
framework_ref: ${{ needs.parse.outputs.head_sha }}

build_mu_linux:
name: MuseScore Linux
needs: parse
if: contains(needs.parse.outputs.mu_platforms, 'linux_x64') || contains(needs.parse.outputs.mu_platforms, 'linux_arm64')
if: contains(needs.parse.outputs.mu_platforms, 'linux_x64')
uses: musescore/MuseScore/.github/workflows/build_linux.yml@main
Comment on lines 107 to 111

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- pr_dispatch.yml around lines 95-120 ---\n'
sed -n '95,120p' .github/workflows/pr_dispatch.yml

printf '\n--- search for pinning/zizmor references ---\n'
rg -n "zizmor|pinning|commit SHA|sha" .github . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' || true

printf '\n--- list workflow files mentioning reusable workflow refs ---\n'
rg -n "uses: .*`@main`|uses: .*`@master`|uses: .*`@v`[0-9]|uses: .*@[0-9a-f]{7,}" .github/workflows || true

Repository: musescore/muse_framework

Length of output: 253


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate likely policy/config files and the workflow file.
git ls-files '.github/**' '*.md' '*zizmor*' '*security*' '*policy*' | sed -n '1,200p'

printf '\n--- workflow ref lines in .github/workflows/pr_dispatch.yml ---\n'
nl -ba .github/workflows/pr_dispatch.yml | sed -n '100,115p'

Repository: musescore/muse_framework

Length of output: 2192


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the workflow file and any repo pinning guidance.
nl -ba .github/workflows/pr_dispatch.yml | sed -n '100,115p'
printf '\n--- pinning guidance ---\n'
rg -n "zizmor|pinning|commit SHA|SHA|reusable workflow|workflow_call" .github *.md || true

Repository: musescore/muse_framework

Length of output: 202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

path = Path('.github/workflows/pr_dispatch.yml')
lines = path.read_text().splitlines()
for i in range(100, 116):
    if i <= len(lines):
        print(f"{i}: {lines[i-1]}")
PY

printf '\n--- search for pinning / zizmor / reusable workflow guidance ---\n'
rg -n "zizmor|pinning|commit SHA|immutable SHA|reusable workflow|workflow_call|`@main`|`@master`|@[0-9a-f]{7,}" .github ./*.md ./*/*.md || true

printf '\n--- reusable workflow refs in .github/workflows ---\n'
python3 - <<'PY'
from pathlib import Path
import re

for path in Path('.github/workflows').glob('*.yml'):
    text = path.read_text(errors='ignore')
    if re.search(r'uses:\s+.*@(?:main|master|v\d|[0-9a-f]{7,40})', text):
        print(f'FILE: {path}')
        for i, line in enumerate(text.splitlines(), 1):
            if 'uses:' in line and '@' in line:
                print(f'{i}: {line}')
PY

Repository: musescore/muse_framework

Length of output: 2618


Pin this reusable workflow to a commit SHA musescore/MuseScore/.github/workflows/build_linux.yml@main is mutable, so upstream changes can alter this pipeline without a PR here. Same applies to the other reusable workflow refs in this file.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 111-111: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr_dispatch.yml around lines 107 - 111, The reusable
workflow references in this workflow are using mutable branch refs, which can
change behavior without updates here. Update the `uses` targets in
`.github/workflows/pr_dispatch.yml` (including `build_mu_linux` and the other
reusable workflow calls in this file) to fixed commit SHAs instead of `@main`,
so the pipeline is pinned to an immutable version.

Source: Linters/SAST tools

with:
app_repo: ${{ needs.parse.outputs.mu_owner }}/${{ needs.parse.outputs.mu_repo }}
app_ref: ${{ needs.parse.outputs.mu_branch }}
framework_repo: ${{ github.repository }}
framework_ref: ${{ needs.parse.outputs.head_sha }}
platforms: ${{ needs.parse.outputs.mu_platforms }}
platforms: linux_x64
build_mode: devel
Comment on lines 107 to 118

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pass a Windows-only platform list after splitting the MuseScore jobs.

Line 117 correctly narrows the Linux reusable workflow to linux_x64, but build_mu_windows still receives the raw mu_platforms string. A PR requesting linux_x64 macos windows_x64 will now send non-Windows tokens into build_windows.yml, which breaks the new per-platform routing contract. Compute a dedicated Windows subset in parse and pass that output to the Windows job instead.

Suggested fix
     outputs:
       head_sha: ${{ steps.pr.outputs.head_sha }}
       au_owner: ${{ steps.cfg.outputs.au_owner }}
       au_repo: ${{ steps.cfg.outputs.au_repo }}
       au_branch: ${{ steps.cfg.outputs.au_branch }}
       au_platforms: ${{ steps.cfg.outputs.au_platforms }}
       mu_owner: ${{ steps.cfg.outputs.mu_owner }}
       mu_repo: ${{ steps.cfg.outputs.mu_repo }}
       mu_branch: ${{ steps.cfg.outputs.mu_branch }}
       mu_platforms: ${{ steps.cfg.outputs.mu_platforms }}
+      mu_windows_platforms: ${{ steps.cfg.outputs.mu_windows_platforms }}
...
           mu_platforms=$(extract "musescore platforms")
           [ -z "$mu_platforms" ] && mu_platforms="linux_x64 macos windows_x64"
+          mu_windows_platforms=$(
+            printf '%s\n' "$mu_platforms" | tr ' ' '\n' |
+              grep -E '^(windows_x64|windows_portable)$' |
+              paste -sd' ' - || true
+          )
           read -r mu_owner mu_repo mu_branch < <(parse_combo "musescore" "$mu_combo" "musescore/MuseScore/main")
           {
             echo "mu_owner=$mu_owner"
             echo "mu_repo=$mu_repo"
             echo "mu_branch=$mu_branch"
             echo "mu_platforms=$mu_platforms"
+            echo "mu_windows_platforms=$mu_windows_platforms"
           } >> "$GITHUB_OUTPUT"
...
   build_mu_windows:
     name: MuseScore Windows
     needs: parse
     if: contains(needs.parse.outputs.mu_platforms, 'windows_x64') || contains(needs.parse.outputs.mu_platforms, 'windows_portable')
     uses: musescore/MuseScore/.github/workflows/build_windows.yml@main
     with:
...
-      platforms: ${{ needs.parse.outputs.mu_platforms }}
+      platforms: ${{ needs.parse.outputs.mu_windows_platforms }}
       build_mode: devel
🧰 Tools
🪛 zizmor (1.26.1)

[error] 111-111: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr_dispatch.yml around lines 107 - 118, The Windows
reusable workflow in build_mu_windows is still receiving the unsplit
mu_platforms value, so update parse to emit a Windows-only platform output and
wire build_mu_windows to that output instead of the raw list. Keep
build_mu_linux and the other split jobs using their dedicated subsets, and
locate the change around the parse job outputs plus the build_mu_windows uses
block in pr_dispatch.yml.


build_mu_macos:
name: MuseScore macOS
needs: parse
if: contains(needs.parse.outputs.mu_platforms, 'macos')
uses: musescore/MuseScore/.github/workflows/build_macos.yml@main
Expand All @@ -159,6 +130,7 @@ jobs:
build_mode: devel

build_mu_windows:
name: MuseScore Windows
needs: parse
if: contains(needs.parse.outputs.mu_platforms, 'windows_x64') || contains(needs.parse.outputs.mu_platforms, 'windows_portable')
uses: musescore/MuseScore/.github/workflows/build_windows.yml@main
Expand All @@ -169,39 +141,3 @@ jobs:
framework_ref: ${{ needs.parse.outputs.head_sha }}
platforms: ${{ needs.parse.outputs.mu_platforms }}
build_mode: devel

report:
needs: [parse, build_au_linux, build_au_macos, build_au_windows, build_mu_linux, build_mu_macos, build_mu_windows]
if: always() && needs.parse.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Forward final statuses to PR head
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SHA: ${{ needs.parse.outputs.head_sha }}
AU_LINUX: ${{ needs.build_au_linux.result }}
AU_MACOS: ${{ needs.build_au_macos.result }}
AU_WINDOWS: ${{ needs.build_au_windows.result }}
MU_LINUX: ${{ needs.build_mu_linux.result }}
MU_MACOS: ${{ needs.build_mu_macos.result }}
MU_WINDOWS: ${{ needs.build_mu_windows.result }}
run: |
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
post() {
local context="$1" result="$2" state
case "$result" in
success) state=success ;;
skipped) return 0 ;;
cancelled) state=error ;;
*) state=failure ;;
esac
gh api -X POST "repos/${{ github.repository }}/statuses/$SHA" \
-f state="$state" -f context="$context" -f target_url="$RUN_URL" \
-f description="$context: $result"
}
post "build / Audacity Linux" "$AU_LINUX"
post "build / Audacity macOS" "$AU_MACOS"
post "build / Audacity Windows" "$AU_WINDOWS"
post "build / MuseScore Linux" "$MU_LINUX"
post "build / MuseScore macOS" "$MU_MACOS"
post "build / MuseScore Windows" "$MU_WINDOWS"
Loading