Skip to content

Add PR report preview workflow with Netlify deployment#3494

Open
ewels wants to merge 7 commits into
mainfrom
claude/investigate-pr-multiqc-automation-T8eOV
Open

Add PR report preview workflow with Netlify deployment#3494
ewels wants to merge 7 commits into
mainfrom
claude/investigate-pr-multiqc-automation-T8eOV

Conversation

@ewels
Copy link
Copy Markdown
Member

@ewels ewels commented Feb 21, 2026

Adds a GitHub Actions workflow that:

  • Detects which MultiQC modules were modified in a PR
  • Runs MultiQC with only those modules against test data
  • Deploys the HTML report to Netlify as a draft deploy
  • Posts a sticky comment on the PR with the report link

https://claude.ai/code/session_01JjK4tKCRqVqd5qrYv19SLb

Adds a GitHub Actions workflow that:
- Detects which MultiQC modules were modified in a PR
- Runs MultiQC with only those modules against test data
- Deploys the HTML report to Netlify as a draft deploy
- Posts a sticky comment on the PR with the report link

https://claude.ai/code/session_01JjK4tKCRqVqd5qrYv19SLb
The single-workflow approach couldn't work for fork PRs because
`pull_request` events from forks don't have access to repo secrets.

Split into:
- pr_report_preview.yml (build): runs on `pull_request`, builds the
  report, saves PR metadata, uploads everything as an artifact. No
  secrets needed, safe for forks.
- pr_report_deploy.yml (deploy): runs on `workflow_run` when the
  build completes, downloads the artifact, deploys to Netlify, and
  posts the PR comment. Runs in the base repo context with secret
  access.

https://claude.ai/code/session_01JjK4tKCRqVqd5qrYv19SLb
- Remove automatic code review on PR open — /review comment is now
  the only way to trigger it
- Add /preview command to trigger report preview builds
- Consolidate into 3 workflows (from 4):
  1. Trigger: fires on /review or /preview PR comments, saves PR
     metadata and which command(s) were requested
  2. Review: workflow_run consumer, gates on /review command
  3. Preview: workflow_run consumer, gates on /preview command,
     builds report + deploys to Netlify in one job
- Delete separate pr_report_deploy.yml (merged into preview workflow)
- Both /review and /preview work on fork PRs since workflow_run
  always executes in the base repo context with access to secrets

https://claude.ai/code/session_01JjK4tKCRqVqd5qrYv19SLb
Add instructions for the PR automation commands to:
- PR template: small <sup> note at the bottom prompting contributors
  to use the commands when ready
- .github/CONTRIBUTING.md: new "PR automation commands" section under
  the review workflow
- docs/markdown/development/contributing.md: new section at the top
  of the contributing guide

https://claude.ai/code/session_01JjK4tKCRqVqd5qrYv19SLb
Comment on lines +111 to +115
- name: "Install MultiQC"
if: steps.detect.outputs.has_modules == 'true'
run: pip install .

- name: "Download test data"

Check failure

Code scanning / CodeQL

Checkout of untrusted code in a privileged context Critical

Potential execution of untrusted code on a privileged workflow (
workflow_run
)

- name: "Install MultiQC"
if: steps.detect.outputs.has_modules == 'true'
run: pip install .

Check failure

Code scanning / CodeQL

Artifact poisoning Critical

Potential artifact poisoning in
pip install .
, which may be controlled by an external user (
workflow_run
).

Copilot Autofix

AI 3 months ago

In general, to fix artifact poisoning you should never let downloaded artifacts overwrite files that will later be executed or installed. Instead, extract artifacts into a dedicated temporary directory, and only read specific files from there after validating their contents. Keep the repository tree (from actions/checkout) separate from artifact contents, especially when running commands like pip install . that execute code from the local filesystem.

For this workflow, the best fix with minimal behavioral change is:

  1. Explicitly create a temporary subdirectory under ${{ runner.temp }} (e.g., ${{ runner.temp }}/pr-info) before downloading the artifact.
  2. Configure actions/download-artifact@v4 to extract the pr-info artifact into that temp directory via its path input, so it cannot overwrite any files in the repository working directory.
  3. Update the “Check if preview was requested” step to read run-preview, pr-number.txt, and head-sha.txt from the temp directory instead of the workspace root.
  4. Ensure that all downstream uses of PR_NUMBER and HEAD_SHA continue to rely on the validated values via step outputs, which is already the case; no change needed there.
  5. Leave pip install . unchanged, as it will then install from a repository that cannot have been modified by the downloaded artifact.

Concretely, in .github/workflows/pr_report_preview.yml you’ll add a step to create ${{ runner.temp }}/pr-info and modify the existing “Download PR info” and “Check if preview was requested” steps to use that directory.

Suggested changeset 1
.github/workflows/pr_report_preview.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/pr_report_preview.yml b/.github/workflows/pr_report_preview.yml
--- a/.github/workflows/pr_report_preview.yml
+++ b/.github/workflows/pr_report_preview.yml
@@ -19,24 +19,30 @@
       pull-requests: write
 
     steps:
+      - name: Prepare temp directory for PR info
+        run: mkdir -p "${{ runner.temp }}/pr-info"
+
       - name: Download PR info
         uses: actions/download-artifact@v4
         with:
           name: pr-info
           github-token: ${{ github.token }}
           run-id: ${{ github.event.workflow_run.id }}
+          path: ${{ runner.temp }}/pr-info
 
       - name: Check if preview was requested
         id: pr-info
         run: |
-          if [ ! -f run-preview ]; then
+          ARTIFACT_DIR="${{ runner.temp }}/pr-info"
+
+          if [ ! -f "$ARTIFACT_DIR/run-preview" ]; then
             echo "Not a /preview request, skipping"
             echo "skip=true" >> "$GITHUB_OUTPUT"
             exit 0
           fi
 
-          PR_NUMBER=$(cat pr-number.txt)
-          HEAD_SHA=$(cat head-sha.txt)
+          PR_NUMBER=$(cat "$ARTIFACT_DIR/pr-number.txt")
+          HEAD_SHA=$(cat "$ARTIFACT_DIR/head-sha.txt")
 
           # Validate PR number is a positive integer
           if ! echo "$PR_NUMBER" | grep -qE '^[0-9]+$'; then
EOF
@@ -19,24 +19,30 @@
pull-requests: write

steps:
- name: Prepare temp directory for PR info
run: mkdir -p "${{ runner.temp }}/pr-info"

- name: Download PR info
uses: actions/download-artifact@v4
with:
name: pr-info
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}
path: ${{ runner.temp }}/pr-info

- name: Check if preview was requested
id: pr-info
run: |
if [ ! -f run-preview ]; then
ARTIFACT_DIR="${{ runner.temp }}/pr-info"

if [ ! -f "$ARTIFACT_DIR/run-preview" ]; then
echo "Not a /preview request, skipping"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi

PR_NUMBER=$(cat pr-number.txt)
HEAD_SHA=$(cat head-sha.txt)
PR_NUMBER=$(cat "$ARTIFACT_DIR/pr-number.txt")
HEAD_SHA=$(cat "$ARTIFACT_DIR/head-sha.txt")

# Validate PR number is a positive integer
if ! echo "$PR_NUMBER" | grep -qE '^[0-9]+$'; then
Copilot is powered by AI and may make mistakes. Always verify output.
Comment thread .github/workflows/pr_report_preview.yml Fixed
Comment thread .github/workflows/pr_report_preview.yml Fixed
Address GitHub Advanced Security findings:
- Move all step output expressions out of run: blocks into env:
  variables to prevent GitHub Actions template injection
- Add validation that PR number is a positive integer
- Add validation that HEAD SHA is a 40-char hex string
- Add validation that module names contain only safe characters
  (alphanumeric, hyphens, underscores)

The "checkout of untrusted code" and "artifact poisoning" findings
are inherent to the feature (we must install the PR's code to build
the report) and cannot be mitigated without removing the feature.

https://claude.ai/code/session_01JjK4tKCRqVqd5qrYv19SLb
The build workflow (pr_report_preview.yml) checks out and runs
untrusted PR code but has NO access to deployment secrets — only
a read-only GITHUB_TOKEN. The deploy workflow (pr_report_deploy.yml)
has access to Netlify secrets but NEVER checks out or executes
untrusted code — it only deploys pre-built HTML from an artifact.

This 3-step chain (trigger → build → deploy) ensures:
- Untrusted code never runs alongside deployment credentials
- Deployment secrets are never exposed to PR authors
- The HTML artifact is the only thing that crosses the boundary

https://claude.ai/code/session_01JjK4tKCRqVqd5qrYv19SLb
Comment thread .github/workflows/pr_report_preview.yml Fixed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants