Skip to content

ci: add workflow_dispatch trigger to PR Preview for Dependabot PRs#326

Merged
Aureliolo merged 4 commits intomainfrom
ci/preview-dispatch
Mar 12, 2026
Merged

ci: add workflow_dispatch trigger to PR Preview for Dependabot PRs#326
Aureliolo merged 4 commits intomainfrom
ci/preview-dispatch

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Add workflow_dispatch trigger to pages-preview.yml so PR previews can be manually triggered for Dependabot PRs (which don't get pull_request events with secrets access)
  • Add pr_number input parameter and resolve PR metadata (head SHA) dynamically via gh pr view for dispatch runs
  • Propagate PR number and head SHA through job outputs so deploy + comment steps work correctly for both trigger types
  • Guard cleanup job with github.event_name == 'pull_request' check so dispatch runs don't accidentally trigger cleanup

Test plan

  • Trigger workflow manually via Actions UI with a valid PR number — verify build + deploy + comment all succeed
  • Verify normal pull_request trigger still works as before (open/synchronize/close)
  • Verify cleanup job only runs on pull_request close events, not on dispatch
  • Verify preview URL comment is posted to the correct PR when triggered via dispatch

Review coverage

Quick mode (CI-only change, no Python code) — automated checks only, no agent review needed.

Dependabot PRs don't have access to secrets, so the deploy-preview job
always fails. Add a manual workflow_dispatch trigger with a pr_number
input so maintainers can trigger the preview deploy from the Actions tab.

Changes:
- Add workflow_dispatch trigger with required pr_number input
- Add resolve step to fetch PR metadata (number + head SHA) for both triggers
- Propagate resolved values via job outputs to deploy and comment steps
- Guard cleanup job to only run on pull_request closed events
Copilot AI review requested due to automatic review settings March 12, 2026 17:44
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Note

Gemini is unable to generate a summary for this pull request due to the file types involved not being currently supported.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 12, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 12, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0b627111-7195-46c2-b9e3-e9fb1b0efc06

📥 Commits

Reviewing files that changed from the base of the PR and between 9fe5df0 and 1c0543c.

📒 Files selected for processing (1)
  • .github/workflows/pages-preview.yml

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Manual trigger for preview deployments via a required PR number input.
  • Improvements

    • Resolves and validates PR metadata for both manual and PR-triggered previews; rejects closed or cross-repo PRs.
    • Uses resolved metadata for consistent preview URLs, banner injection, comments, and deploys.
    • Skips deployments for forked PRs; cleanup runs only for pull_request events.
    • Better concurrency and failure handling for more reliable preview runs.

Walkthrough

Adds a manual workflow_dispatch trigger to the pages-preview workflow, resolves and validates PR metadata (pr_number, head_sha, same_repo) for both dispatch and pull_request events, and propagates those outputs through build, deploy, comment, and cleanup steps to normalize handling across dispatch vs PR runs.

Changes

Cohort / File(s) Summary
Workflow: preview pipeline
/.github/workflows/pages-preview.yml
Add workflow_dispatch with required pr_number input; add "Resolve PR metadata" step to fetch/validate PR (OPEN) and derive pr_number, head_sha, same_repo; use those outputs for checkout, banner injection, deploy, comment, and cleanup; refine job conditions to skip cross-repo/fork deployments and limit cleanup to pull_request events.
Docs: CI notes
CLAUDE.md
Document dispatch-based preview flow, PR metadata resolution/validation (positive integer, open state), same-repo check, preview URL pattern, required secrets, and updated cleanup semantics.

Sequence Diagram(s)

sequenceDiagram
  participant User as Trigger (user)
  participant GH as GitHub Actions
  participant GHAPI as GitHub API (gh)
  participant Runner as Actions runner
  participant Pages as Cloudflare Pages (wrangler)
  participant Comments as GitHub Issues API

  rect rgba(100,149,237,0.5)
    User->>GH: workflow_dispatch(pr_number) or pull_request event
  end

  GH->>GHAPI: Resolve PR metadata (pr_number, head_sha, state, repo)
  GHAPI-->>GH: metadata (pr_number, head_sha, same_repo)
  GH->>Runner: start Build job with pr metadata outputs
  Runner->>Runner: checkout @ head_sha
  Runner->>Pages: build & deploy preview (if same_repo && PR open)
  Pages-->>Runner: preview URL
  Runner->>Comments: post/update PR comment with preview URL
  Note over GH: On PR close (pull_request only) -> Cleanup job deletes preview and comment
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding workflow_dispatch trigger to enable PR previews for Dependabot PRs that lack pull_request event access.
Description check ✅ Passed The description is directly related to the changeset, explaining the purpose, implementation details, testing approach, and validation for the workflow modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ci/preview-dispatch
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch ci/preview-dispatch
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.64%. Comparing base (d1203e5) to head (1c0543c).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #326   +/-   ##
=======================================
  Coverage   93.64%   93.64%           
=======================================
  Files         427      427           
  Lines       19177    19177           
  Branches     1846     1846           
=======================================
  Hits        17959    17959           
  Misses        943      943           
  Partials      275      275           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 12, 2026

Greptile Summary

This PR adds a workflow_dispatch trigger to pages-preview.yml so that PR previews can be manually triggered for Dependabot PRs, which don't receive pull_request events with secrets access. The core change introduces a "Resolve PR metadata" step that dynamically fetches the PR's head SHA, state, and cross-repository flag via gh pr view for dispatch runs, while falling back to event context for normal pull_request runs. Job outputs (pr_number, head_sha, same_repo) are threaded through to the deploy and comment steps, replacing previous direct event context references.

Key changes:

  • workflow_dispatch input added with integer validation (^[1-9][0-9]*$) and state guard (PR_STATE != 'OPEN' → fast-fail)
  • pull-requests: read added to the build job's permissions to allow gh pr view on the github.token
  • User-supplied pr_number input is safely passed via an env: variable (PR_INPUT), avoiding inline shell interpolation
  • deploy-preview condition updated from github.event.pull_request.head.repo.full_name == github.repository to needs.build.outputs.same_repo == 'true', which correctly handles dispatch runs where github.event.pull_request is null
  • cleanup-preview guarded with github.event_name == 'pull_request' to prevent accidental cleanup on dispatch runs
  • Concurrency group updated with || github.event.inputs.pr_number fallback for dispatch runs

One issue found: the if [ -z "$PR_JSON" ] guard after gh pr view is unreachable under set -euo pipefail — if gh exits non-zero the script exits immediately, so the custom ::error:: annotation never fires. A minor consistency note on the PREVIEW_URL env var using inline ${{ }} template expansion (safe given digit-only validation, but inconsistent with the rest of the step's safe env-var pattern).

Confidence Score: 4/5

  • This PR is safe to merge; the implementation is sound and the previously identified critical issues (permissions, input injection) have both been resolved.
  • Both previously-flagged critical issues are resolved: pull-requests: read is present and user input is passed via env vars. The remaining issues are minor: an unreachable dead-code guard and a style inconsistency in URL construction. The core logic for dispatch vs pull_request branching, PR state validation, and job output propagation is correct.
  • .github/workflows/pages-preview.yml lines 64-67 (unreachable empty-JSON guard) warrants a quick look, but it is not a blocking issue.

Important Files Changed

Filename Overview
.github/workflows/pages-preview.yml Adds workflow_dispatch trigger with proper env-var-based input handling, PR metadata resolution via gh pr view, job output propagation, and dispatch-guard on cleanup. Two minor issues: the if [ -z "$PR_JSON" ] check is unreachable under set -euo pipefail, and a ${{ }} inline expression is used to construct PREVIEW_URL (safe due to digit validation but inconsistent with the rest of the safe pattern).
CLAUDE.md Documentation-only update to the PR Preview section of CLAUDE.md, accurately reflecting the new workflow_dispatch trigger, Dependabot use case, dispatch metadata resolution, and updated cleanup guard. No issues.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([Trigger]) --> B{Event type?}
    B -->|workflow_dispatch| C[Read pr_number input]
    B -->|pull_request| D[Read pr_number from event context]

    C --> E[gh pr view PR_NUMBER --json headRefOid,state,isCrossRepository]
    E --> F{PR state == OPEN?}
    F -->|No| FAIL1([Exit with error])
    F -->|Yes| G[Resolve head_sha and same_repo from API response]

    D --> H[Read head_sha and same_repo from event context]

    G --> VALIDATE
    H --> VALIDATE

    VALIDATE[Validate pr_number is positive integer] --> BUILD
    BUILD[Build MkDocs + Astro, inject preview banner] --> OUTPUTS
    OUTPUTS[Emit pr_number, head_sha, same_repo as job outputs]

    OUTPUTS --> I{same_repo == true?}
    I -->|No| SKIP([deploy-preview skipped])
    I -->|Yes| DEPLOY[Deploy to Cloudflare Pages branch pr-N]
    DEPLOY --> COMMENT[Create or update PR preview comment]

    OUTPUTS --> J{pull_request closed?}
    J -->|No| END([Done])
    J -->|Yes| CLEANUP[Delete preview comment + Cloudflare deployments]
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: .github/workflows/pages-preview.yml
Line: 64-67

Comment:
**Unreachable empty-JSON guard with `set -euo pipefail`**

The `if [ -z "$PR_JSON" ]` guard is effectively unreachable in any failure path. With `set -euo pipefail` active, if `gh pr view` exits non-zero (e.g., PR not found, auth error, network failure), bash exits immediately during the command substitution — `PR_JSON` is never assigned, and execution never reaches the empty check. The custom `::error::` annotation will therefore never fire; the workflow will just exit with gh's own error output.

If you want the custom error message to appear, you need to suppress the exit or capture the exit code explicitly:

```suggestion
            CURL_EXIT=0
            PR_JSON=$(gh pr view "$PR_NUMBER" --repo "$GH_REPOSITORY" \
              --json headRefOid,state,isCrossRepository 2>&1) || CURL_EXIT=$?
            if [ "$CURL_EXIT" -ne 0 ] || [ -z "$PR_JSON" ]; then
              echo "::error::Failed to fetch metadata for PR #$PR_NUMBER (exit $CURL_EXIT)"
              exit 1
            fi
```

Or alternatively, simply remove the dead guard and rely on gh's own error output (which is already clear) plus the `set -e` exit.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: .github/workflows/pages-preview.yml
Line: 237

Comment:
**Inline `${{ }}` expression in `env:` value for URL construction**

`${{ needs.build.outputs.pr_number }}` is used inline within the YAML string value of the `PREVIEW_URL` env var. While the value is validated upstream as `^[1-9][0-9]*$` (digits only), this is still an inline template expansion. Since `pr_number` can only be digits at this point, there is no actual injection risk here — but for consistency with the safe pattern used in the build step's `PR_INPUT` env var, consider assembling the URL in the script itself:

```suggestion
          PREVIEW_URL: ""
          PR_NUMBER: ${{ needs.build.outputs.pr_number }}
          HEAD_SHA: ${{ needs.build.outputs.head_sha }}
```

Then construct the URL in the `script:` block: `const url = \`https://pr-${prNumber}.synthorg-pr-preview.pages.dev\`;`. This is a minor consistency suggestion since `pr_number` is already digit-validated.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 1c0543c

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the PR Preview GitHub Actions workflow to support manual workflow_dispatch runs (primarily to enable previews for Dependabot PRs that don’t receive secret-bearing pull_request events), while keeping existing PR-triggered behavior working.

Changes:

  • Adds a workflow_dispatch trigger with a pr_number input and resolves the PR head SHA dynamically via gh pr view.
  • Propagates pr_number and head_sha through build job outputs and updates checkout/banner/commenting to use those values.
  • Prevents the cleanup job from running on dispatch executions.

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

Comment on lines 35 to +49
permissions:
contents: read
outputs:
pr_number: ${{ steps.pr.outputs.pr_number }}
head_sha: ${{ steps.pr.outputs.head_sha }}
steps:
- name: Resolve PR metadata
id: pr
env:
GH_TOKEN: ${{ github.token }}
run: |
PR_NUMBER="${{ github.event.pull_request.number || github.event.inputs.pr_number }}"
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
HEAD_SHA=$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json headRefOid -q .headRefOid)
else
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The new gh pr view call will require pull-requests: read permission on the job token. Right now the workflow-level permissions are {} and this job only grants contents: read, so gh pr view is likely to 403 on workflow_dispatch runs. Add pull-requests: read (or move it to workflow-level if preferred) so PR metadata resolution works reliably.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +58
run: |
PR_NUMBER="${{ github.event.pull_request.number || github.event.inputs.pr_number }}"
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
HEAD_SHA=$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json headRefOid -q .headRefOid)
else
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
fi
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha }}
ref: ${{ steps.pr.outputs.head_sha }}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The metadata resolution step doesn’t fail fast if the PR number is invalid or if gh pr view returns an empty HEAD_SHA. In that case actions/checkout will fall back to the default ref and you may deploy/comment against the wrong commit. Consider adding set -euo pipefail and explicit validation (non-empty PR_NUMBER and HEAD_SHA, with a clear error) before writing outputs.

Copilot uses AI. Check for mistakes.
Comment on lines +170 to +172
if: >-
github.event_name == 'workflow_dispatch' ||
github.event.pull_request.head.repo.full_name == github.repository
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

deploy-preview now runs unconditionally for workflow_dispatch, bypassing the existing same-repo guard. That makes it possible to deploy previews (using the Cloudflare token) for fork-based PRs if someone manually dispatches the workflow with that PR number. Consider enforcing the same-repo check for dispatch too (e.g., resolve headRepository.fullName in the metadata step and fail/skip when it doesn’t match github.repository, or add an explicit input to allow forks).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/pages-preview.yml:
- Around line 41-53: The workflow reads PR_NUMBER from untrusted inputs and may
preserve leading zeros; sanitize and canonicalize PR_NUMBER after it's set in
the step with id "pr": validate it is an integer, strip any leading zeros to
produce a canonical decimal (e.g., "00123" -> "123"), and fail the job if the
result is empty or non-numeric; then write the sanitized PR_NUMBER to
GITHUB_OUTPUT (instead of the raw value) so downstream uses and cleanup keys
(e.g., the preview branch name) use the canonical form while leaving HEAD_SHA
handling unchanged.
- Around line 170-172: The current job conditional short-circuits eligibility
for manual runs; add a metadata step that resolves the pull_request state and
whether the head repo matches the target repo (set outputs like pr_state and
same_repo) and then change the job if to require those outputs for
workflow_dispatch as well (e.g. only proceed when
needs.metadata.outputs.pr_state == 'open' and needs.metadata.outputs.same_repo
== 'true'); update the conditional that currently uses
workflow_dispatch/pull_request head checks so both manual dispatches and PR
events are gated by the metadata outputs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2460c43a-0a77-4cc2-81d4-b4e153e6cd93

📥 Commits

Reviewing files that changed from the base of the PR and between d1203e5 and e8a0464.

📒 Files selected for processing (1)
  • .github/workflows/pages-preview.yml
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Agent
  • GitHub Check: Greptile Review
🧰 Additional context used
🪛 GitHub Actions: Workflow Security
.github/workflows/pages-preview.yml

[error] 46-46: template-injection: code injection via template expansion. PR_NUMBER expansion uses a potentially controllable expression '${{ github.event.pull_request.number || github.event.inputs.pr_number }}' which may expand into attacker-controlled code.

Comment on lines +170 to +172
if: >-
github.event_name == 'workflow_dispatch' ||
github.event.pull_request.head.repo.full_name == github.repository
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

Keep workflow_dispatch deploys limited to open, same-repo PRs.

Line 170 short-circuits the old eligibility checks for every manual run. That means a closed PR can get a fresh preview/comment even though cleanup-preview never runs for dispatch, and cross-repo PRs are no longer filtered out. Resolve the PR state/repo in the metadata step and gate this job on those outputs as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pages-preview.yml around lines 170 - 172, The current job
conditional short-circuits eligibility for manual runs; add a metadata step that
resolves the pull_request state and whether the head repo matches the target
repo (set outputs like pr_state and same_repo) and then change the job if to
require those outputs for workflow_dispatch as well (e.g. only proceed when
needs.metadata.outputs.pr_state == 'open' and needs.metadata.outputs.same_repo
== 'true'); update the conditional that currently uses
workflow_dispatch/pull_request head checks so both manual dispatches and PR
events are gated by the metadata outputs.

…ions

- Route all user-controlled inputs through env vars instead of inline
  ${{ }} interpolation in run blocks (fixes zizmor template-injection)
- Add pull-requests: read permission for gh pr view in build job
- Validate pr_number is a positive integer (rejects non-numeric and
  leading zeros)
- Fail fast if gh pr view or HEAD SHA resolution fails
- Check PR is OPEN and same-repo on workflow_dispatch (prevents
  deploying previews for closed or cross-repo PRs)
- Unify deploy-preview condition via same_repo job output
- Update CLAUDE.md PR Preview docs to reflect dispatch behavior

Addresses: zizmor CI failure, Greptile, Copilot, and CodeRabbit review
feedback (6 findings: 2 critical, 4 major)
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/pages-preview.yml:
- Around line 43-88: The jq -r call extracting headRefOid can yield the literal
string "null" which bypasses the current empty check for HEAD_SHA; update the
logic around PR_JSON and the HEAD_SHA extraction (the jq -r '.headRefOid'
invocation and the subsequent HEAD_SHA variable) to treat both empty string and
the literal "null" as missing (e.g., after setting HEAD_SHA, test if HEAD_SHA is
empty or equals "null" and fail with the existing error path), and apply the
same "null" check pattern for any other jq-extracted variables you rely on (like
IS_CROSS_REPO or PR_STATE) so null JSON fields are treated as absent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b9c7898b-ce96-4679-a098-61ce613c355e

📥 Commits

Reviewing files that changed from the base of the PR and between e8a0464 and c03bea1.

📒 Files selected for processing (2)
  • .github/workflows/pages-preview.yml
  • CLAUDE.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-03-12T11:06:11.009Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-12T11:06:11.009Z
Learning: NEVER create a PR directly with `gh pr create` — ALWAYS use `/pre-pr-review` to create PRs which runs automated checks + review agents + fixes before creating the PR

Applied to files:

  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-12T11:06:11.009Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-12T11:06:11.009Z
Learning: When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes), fix them all — no deferring or 'out of scope' skipping

Applied to files:

  • .github/workflows/pages-preview.yml
🔇 Additional comments (7)
.github/workflows/pages-preview.yml (6)

16-21: LGTM!

The workflow_dispatch trigger with required pr_number input is well-defined for enabling manual previews of Dependabot PRs.


26-27: LGTM!

The concurrency group correctly derives the PR number from either event type, ensuring stale builds are cancelled appropriately for both triggers.


202-205: LGTM!

The deploy-preview job is now properly gated on same_repo == 'true', addressing the earlier review concern. Combined with the OPEN-state validation in the build job (which fails fast for closed PRs), this ensures dispatch-triggered deploys are limited to open, same-repo PRs.


228-278: LGTM!

The deploy and comment steps correctly consume PR metadata from build outputs. The issue_number for the GitHub API call properly uses the resolved prNumber, ensuring comments are posted to the correct PR regardless of trigger type.


284-287: LGTM!

The cleanup job correctly guards against workflow_dispatch runs. Since dispatch events have no "close" action, limiting cleanup to pull_request events is the right approach.


324-330: LGTM!

Using github.event.pull_request.number directly is appropriate here since the cleanup job is gated to pull_request events only (line 285), making the event context guaranteed to be available and trusted.

CLAUDE.md (1)

184-194: Documentation accurately reflects the workflow changes.

The updated PR Preview section correctly documents:

  • The new workflow_dispatch trigger with pr_number input
  • Metadata resolution paths for both event types
  • Validation rules and fail-fast behavior
  • Same-repo gating for deploys
  • Cleanup restriction to pull_request events

Comment on lines +43 to +88
- name: Resolve PR metadata
id: pr
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
PR_INPUT: ${{ github.event.inputs.pr_number }}
PR_EVENT_NUMBER: ${{ github.event.pull_request.number }}
PR_EVENT_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_EVENT_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
GH_REPOSITORY: ${{ github.repository }}
run: |
PR_NUMBER="${PR_EVENT_NUMBER:-$PR_INPUT}"
if ! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then
echo "::error::PR number must be a positive integer, got: '$PR_NUMBER'"
exit 1
fi

if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
PR_JSON=$(gh pr view "$PR_NUMBER" --repo "$GH_REPOSITORY" \
--json headRefOid,state,isCrossRepository)
if [ -z "$PR_JSON" ]; then
echo "::error::Failed to fetch metadata for PR #$PR_NUMBER"
exit 1
fi
HEAD_SHA=$(echo "$PR_JSON" | jq -r '.headRefOid')
PR_STATE=$(echo "$PR_JSON" | jq -r '.state')
IS_CROSS_REPO=$(echo "$PR_JSON" | jq -r '.isCrossRepository')

if [ "$PR_STATE" != "OPEN" ]; then
echo "::error::PR #$PR_NUMBER is $PR_STATE, not OPEN — cannot deploy preview"
exit 1
fi
SAME_REPO=$( [ "$IS_CROSS_REPO" = "false" ] && echo "true" || echo "false" )
else
HEAD_SHA="$PR_EVENT_HEAD_SHA"
SAME_REPO=$( [ "$PR_EVENT_HEAD_REPO" = "$GH_REPOSITORY" ] && echo "true" || echo "false" )
fi

if [ -z "$HEAD_SHA" ]; then
echo "::error::Failed to resolve HEAD SHA for PR #$PR_NUMBER"
exit 1
fi

echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
echo "same_repo=$SAME_REPO" >> "$GITHUB_OUTPUT"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Well-implemented PR metadata resolution.

This addresses the security concerns from earlier reviews:

  1. Template injection mitigated: User-controlled inputs are routed through environment variables (PR_INPUT, EVENT_NAME), preventing shell injection.
  2. Leading zeros prevented: Regex ^[1-9][0-9]*$ ensures canonical PR numbers (rejects 00123).
  3. Closed PRs rejected: Dispatch runs fail fast if PR is not OPEN.
  4. Cross-repo PRs filtered: same_repo output enables downstream gating.

One minor hardening suggestion: jq returns the literal string "null" for missing/null JSON fields, which would pass the empty check at line 81. Consider adding an explicit check:

🛡️ Optional hardening
+          if [ -z "$HEAD_SHA" ] || [ "$HEAD_SHA" = "null" ]; then
-          if [ -z "$HEAD_SHA" ]; then
             echo "::error::Failed to resolve HEAD SHA for PR #$PR_NUMBER"
             exit 1
           fi

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pages-preview.yml around lines 43 - 88, The jq -r call
extracting headRefOid can yield the literal string "null" which bypasses the
current empty check for HEAD_SHA; update the logic around PR_JSON and the
HEAD_SHA extraction (the jq -r '.headRefOid' invocation and the subsequent
HEAD_SHA variable) to treat both empty string and the literal "null" as missing
(e.g., after setting HEAD_SHA, test if HEAD_SHA is empty or equals "null" and
fail with the existing error path), and apply the same "null" check pattern for
any other jq-extracted variables you rely on (like IS_CROSS_REPO or PR_STATE) so
null JSON fields are treated as absent.

jq -r returns the string "null" for missing JSON fields, which would
pass the empty-string check and cause checkout with an invalid ref.
Copilot AI review requested due to automatic review settings March 12, 2026 18:04
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 12, 2026 18:05 — with GitHub Actions Inactive
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.


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

Comment on lines +60 to +76
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
PR_JSON=$(gh pr view "$PR_NUMBER" --repo "$GH_REPOSITORY" \
--json headRefOid,state,isCrossRepository)
if [ -z "$PR_JSON" ]; then
echo "::error::Failed to fetch metadata for PR #$PR_NUMBER"
exit 1
fi
HEAD_SHA=$(echo "$PR_JSON" | jq -r '.headRefOid')
PR_STATE=$(echo "$PR_JSON" | jq -r '.state')
IS_CROSS_REPO=$(echo "$PR_JSON" | jq -r '.isCrossRepository')

if [ "$PR_STATE" != "OPEN" ]; then
echo "::error::PR #$PR_NUMBER is $PR_STATE, not OPEN — cannot deploy preview"
exit 1
fi
SAME_REPO=$( [ "$IS_CROSS_REPO" = "false" ] && echo "true" || echo "false" )
else
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

In workflow_dispatch runs, cross-repo PRs are not rejected—SAME_REPO is set to false, but the workflow still proceeds with the build job. This conflicts with the PR description/CLAUDE.md text that says cross-repo PRs are rejected. Either fail fast when isCrossRepository is true (exit 1) or update the documentation to say deploy is skipped for cross-repo PRs.

Copilot uses AI. Check for mistakes.
- Builds site on PRs (same path triggers as Pages), injects "Development Preview" banner, deploys to Cloudflare Pages (`synthorg-pr-preview` project) via wrangler CLI
- Builds site on PRs (same path triggers as Pages) and on `workflow_dispatch` (with `pr_number` input, for Dependabot PRs that can't trigger `pull_request` with secrets)
- Dispatch runs resolve PR metadata (head SHA, state, same-repo check) via `gh pr view`; PR-triggered runs use event context directly
- Validates `pr_number` is a positive integer, rejects closed/cross-repo PRs on dispatch, and fails fast if HEAD SHA resolution fails
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

This bullet says dispatch runs "reject closed/cross-repo PRs", but the workflow currently only rejects non-OPEN PRs; cross-repo PRs still build and only skip deploy. Please align this documentation with the actual workflow behavior (or adjust the workflow to fail on cross-repo dispatch runs if that’s intended).

Suggested change
- Validates `pr_number` is a positive integer, rejects closed/cross-repo PRs on dispatch, and fails fast if HEAD SHA resolution fails
- Validates `pr_number` is a positive integer, rejects non-OPEN PRs on dispatch, and fails fast if HEAD SHA resolution fails

Copilot uses AI. Check for mistakes.
@Aureliolo Aureliolo merged commit 4c7b6d9 into main Mar 12, 2026
11 of 12 checks passed
@Aureliolo Aureliolo deleted the ci/preview-dispatch branch March 12, 2026 18:08
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 12, 2026 18:08 — with GitHub Actions Inactive
Comment on lines +64 to +67
if [ -z "$PR_JSON" ]; then
echo "::error::Failed to fetch metadata for PR #$PR_NUMBER"
exit 1
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unreachable empty-JSON guard with set -euo pipefail

The if [ -z "$PR_JSON" ] guard is effectively unreachable in any failure path. With set -euo pipefail active, if gh pr view exits non-zero (e.g., PR not found, auth error, network failure), bash exits immediately during the command substitution — PR_JSON is never assigned, and execution never reaches the empty check. The custom ::error:: annotation will therefore never fire; the workflow will just exit with gh's own error output.

If you want the custom error message to appear, you need to suppress the exit or capture the exit code explicitly:

Suggested change
if [ -z "$PR_JSON" ]; then
echo "::error::Failed to fetch metadata for PR #$PR_NUMBER"
exit 1
fi
CURL_EXIT=0
PR_JSON=$(gh pr view "$PR_NUMBER" --repo "$GH_REPOSITORY" \
--json headRefOid,state,isCrossRepository 2>&1) || CURL_EXIT=$?
if [ "$CURL_EXIT" -ne 0 ] || [ -z "$PR_JSON" ]; then
echo "::error::Failed to fetch metadata for PR #$PR_NUMBER (exit $CURL_EXIT)"
exit 1
fi

Or alternatively, simply remove the dead guard and rely on gh's own error output (which is already clear) plus the set -e exit.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/pages-preview.yml
Line: 64-67

Comment:
**Unreachable empty-JSON guard with `set -euo pipefail`**

The `if [ -z "$PR_JSON" ]` guard is effectively unreachable in any failure path. With `set -euo pipefail` active, if `gh pr view` exits non-zero (e.g., PR not found, auth error, network failure), bash exits immediately during the command substitution — `PR_JSON` is never assigned, and execution never reaches the empty check. The custom `::error::` annotation will therefore never fire; the workflow will just exit with gh's own error output.

If you want the custom error message to appear, you need to suppress the exit or capture the exit code explicitly:

```suggestion
            CURL_EXIT=0
            PR_JSON=$(gh pr view "$PR_NUMBER" --repo "$GH_REPOSITORY" \
              --json headRefOid,state,isCrossRepository 2>&1) || CURL_EXIT=$?
            if [ "$CURL_EXIT" -ne 0 ] || [ -z "$PR_JSON" ]; then
              echo "::error::Failed to fetch metadata for PR #$PR_NUMBER (exit $CURL_EXIT)"
              exit 1
            fi
```

Or alternatively, simply remove the dead guard and rely on gh's own error output (which is already clear) plus the `set -e` exit.

How can I resolve this? If you propose a fix, please make it concise.

uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
PREVIEW_URL: "https://pr-${{ github.event.pull_request.number }}.synthorg-pr-preview.pages.dev"
PREVIEW_URL: "https://pr-${{ needs.build.outputs.pr_number }}.synthorg-pr-preview.pages.dev"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Inline ${{ }} expression in env: value for URL construction

${{ needs.build.outputs.pr_number }} is used inline within the YAML string value of the PREVIEW_URL env var. While the value is validated upstream as ^[1-9][0-9]*$ (digits only), this is still an inline template expansion. Since pr_number can only be digits at this point, there is no actual injection risk here — but for consistency with the safe pattern used in the build step's PR_INPUT env var, consider assembling the URL in the script itself:

Suggested change
PREVIEW_URL: "https://pr-${{ needs.build.outputs.pr_number }}.synthorg-pr-preview.pages.dev"
PREVIEW_URL: ""
PR_NUMBER: ${{ needs.build.outputs.pr_number }}
HEAD_SHA: ${{ needs.build.outputs.head_sha }}

Then construct the URL in the script: block: const url = \https://pr-${prNumber}.synthorg-pr-preview.pages.dev\`;`. This is a minor consistency suggestion since pr_number is already digit-validated.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/pages-preview.yml
Line: 237

Comment:
**Inline `${{ }}` expression in `env:` value for URL construction**

`${{ needs.build.outputs.pr_number }}` is used inline within the YAML string value of the `PREVIEW_URL` env var. While the value is validated upstream as `^[1-9][0-9]*$` (digits only), this is still an inline template expansion. Since `pr_number` can only be digits at this point, there is no actual injection risk here — but for consistency with the safe pattern used in the build step's `PR_INPUT` env var, consider assembling the URL in the script itself:

```suggestion
          PREVIEW_URL: ""
          PR_NUMBER: ${{ needs.build.outputs.pr_number }}
          HEAD_SHA: ${{ needs.build.outputs.head_sha }}
```

Then construct the URL in the `script:` block: `const url = \`https://pr-${prNumber}.synthorg-pr-preview.pages.dev\`;`. This is a minor consistency suggestion since `pr_number` is already digit-validated.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Aureliolo added a commit that referenced this pull request Mar 13, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.3](v0.1.2...v0.1.3)
(2026-03-13)


### Features

* add Mem0 memory backend adapter
([#345](#345))
([2788db8](2788db8)),
closes [#206](#206)
* centralized single-writer TaskEngine with full CRUD API
([#328](#328))
([9c1a3e1](9c1a3e1))
* incremental AgentEngine → TaskEngine status sync
([#331](#331))
([7a68d34](7a68d34)),
closes [#323](#323)
* web dashboard pages — views, components, tests, and review fixes
([#354](#354))
([b165ec4](b165ec4))
* web dashboard with Vue 3 + PrimeVue + Tailwind CSS
([#347](#347))
([06416b1](06416b1))


### Bug Fixes

* harden coordination pipeline with validators, logging, and fail-fast
([#333](#333))
([2f10d49](2f10d49)),
closes [#205](#205)
* repo-wide security hardening from ZAP, Scorecard, and CodeQL audit
([#357](#357))
([27eb288](27eb288))


### CI/CD

* add pip-audit, hadolint, OSSF Scorecard, ZAP DAST, and pre-push hooks
([#350](#350))
([2802d20](2802d20))
* add workflow_dispatch trigger to PR Preview for Dependabot PRs
([#326](#326))
([4c7b6d9](4c7b6d9))
* bump astral-sh/setup-uv from 7.4.0 to 7.5.0 in the minor-and-patch
group ([#335](#335))
([98dd8ca](98dd8ca))


### Maintenance

* bump the minor-and-patch group across 1 directory with 3 updates
([#352](#352))
([031b1c9](031b1c9))
* **deps:** bump devalue from 5.6.3 to 5.6.4 in /site in the
npm_and_yarn group across 1 directory
([#324](#324))
([9a9c600](9a9c600))
* migrate docs build from MkDocs to Zensical
([#330](#330))
([fa8bf1d](fa8bf1d)),
closes [#329](#329)

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
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.

2 participants