Skip to content

ci(vercel): comment the webapp preview URL on the PR#227

Merged
thedavidmeister merged 1 commit into
mainfrom
2026-06-14-vercel-preview-comment
Jun 14, 2026
Merged

ci(vercel): comment the webapp preview URL on the PR#227
thedavidmeister merged 1 commit into
mainfrom
2026-06-14-vercel-preview-comment

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Reviewers currently have to dig the Vercel preview URL out of the Deploy to Vercel job log of the rainix-vercel reusable workflow. Because that reusable uses the Vercel CLI directly (not the Vercel GitHub App), nothing surfaces the URL onto the PR.

This change:

  • Captures the preview URL from the deploy step into a step output (id: deploy). The Vercel CLI prints Preview: https://…vercel.app / Inspect: https://vercel.com/… lines plus the bare deployment URL; we tee the output (so the URLs stay in the log too) and parse the preview URL robustly, preferring the Preview: line and falling back to the last bare *.vercel.app URL. The Inspect URL is captured too.
  • Posts a sticky PR comment via marocchino/sticky-pull-request-comment (pinned to a commit SHA). It uses a stable header keyed on the deploy path, so re-deploys update the same comment in place instead of spamming a new one each run. Body: 🔗 Webapp preview: <url>, the Inspect URL, and the deployed commit SHA.
  • Permissions / gating: adds pull-requests: write to the job (with a note that callers of this reusable must also grant it). The comment step is gated to PR/preview context (github.event_name == 'pull_request' and !inputs.production) and skipped when no URL was captured — so no comment on production / main pushes, only previews.

End-to-end behavior fully confirms only when this runs on a real consumer PR (raindex etc.), since the reusable isn't exercised by rainix's own CI; the URL-capture and sticky-comment logic is gated and idempotent by construction.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Enhanced deployment workflow to automatically capture and post preview URLs and optional inspection dashboard links as sticky comments on pull requests, improving visibility of deployment previews for testing and review purposes. Preview comments are only posted for pull request events, excluding production deployments.

Capture the Vercel preview URL from the deploy step output and post it
as a sticky PR comment (updated in place on each redeploy), so reviewers
don't have to dig it out of the Deploy-Preview job log. Gated to PR /
preview context only — no comment on production/main deploys.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thedavidmeister thedavidmeister self-assigned this Jun 14, 2026
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The workflow gains pull-requests: write permission. The Vercel deploy step is rewritten to capture all output, extract the preview URL and optional inspect URL via regex fallbacks, export them to GITHUB_OUTPUT, and a new conditional step posts a sticky PR comment with those links for pull_request events only.

Changes

Vercel PR Preview Comment

Layer / File(s) Summary
Permissions, URL parsing, and PR comment step
.github/workflows/rainix-vercel.yaml
pull-requests: write permission added; deploy step rewritten to capture and regex-parse preview and inspect URLs into GITHUB_OUTPUT; new conditional step posts a sticky PR comment with those URLs on pull_request events when a preview URL is present.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hippity-hop, the deploy is done,
A sticky comment appears under the sun,
Preview URL parsed with a regex flair,
The rabbit posts links with the greatest of care,
pull-requests: write—now the workflow's complete,
Inspect and preview, oh what a treat! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'ci(vercel): comment the webapp preview URL on the PR' directly and clearly summarizes the main change: adding automatic commenting of Vercel preview URLs to pull requests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2026-06-14-vercel-preview-comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@thedavidmeister

Copy link
Copy Markdown
Contributor Author

Reviewed cafcfee: adds a sticky preview-URL comment step to the rainix-vercel reusable — captures the vercel CLI preview URL and posts/updates ONE sticky comment per PR, gated to PR/preview context (no comment on production), new sticky-comment action SHA-pinned. YAML-valid, purely additive. (Callers must grant pull-requests: write to see the comment — small raindex follow-up.) LGTM.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/rainix-vercel.yaml:
- Around line 127-133: The workflow step injects user inputs directly into the
shell command using GitHub template expression syntax (${{ inputs.production &&
'--prod' || '' }} and ${{ inputs.deploy-path }}), which allows malicious inputs
to break out of quotes and inject arbitrary commands. Add an env section to the
step that captures these inputs as environment variables, then replace the
dangerous template expressions in the shell command with safe shell variable
references using native syntax (e.g., $PRODUCTION_FLAG and $DEPLOY_PATH). This
ensures inputs are safely passed through environment variables rather than being
evaluated as part of the command string construction.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6072ba34-c2fc-4a4e-a4f3-b6a206d41b26

📥 Commits

Reviewing files that changed from the base of the PR and between 49111fe and cafcfee.

📒 Files selected for processing (1)
  • .github/workflows/rainix-vercel.yaml

Comment on lines 127 to +133
run: |
"$VERCEL_BIN" deploy --prebuilt ${{ inputs.production && '--prod' || '' }} --token="$VERCEL_TOKEN" "${{ inputs.deploy-path }}"
# `vercel deploy` prints the bare deployment URL to stdout and the
# human-readable "Preview:"/"Inspect:" lines to stderr. Capture all of
# it (tee back to the log so the URLs stay visible there) and parse the
# URLs robustly from the combined output.
deploy_log="$(mktemp)"
"$VERCEL_BIN" deploy --prebuilt ${{ inputs.production && '--prod' || '' }} --token="$VERCEL_TOKEN" "${{ inputs.deploy-path }}" 2>&1 | tee "$deploy_log"

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

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -name "rainix-vercel.yaml" 2>/dev/null || echo "Not in /tmp, checking root"

Repository: rainlanguage/rainix

Length of output: 45


🏁 Script executed:

git ls-files | grep -E "rainix-vercel|workflows.*yaml"

Repository: rainlanguage/rainix

Length of output: 665


🏁 Script executed:

cat -n .github/workflows/rainix-vercel.yaml 2>/dev/null | sed -n '120,140p'

Repository: rainlanguage/rainix

Length of output: 1447


🌐 Web query:

GitHub Actions template interpolation security ${{ }} in run shell script command injection

💡 Result:

Using the ${{ }} template interpolation syntax directly inside a GitHub Actions run shell script command is a critical security risk that can lead to arbitrary code execution [1][2][3]. The vulnerability occurs because GitHub Actions resolves expressions inside ${{ }} before the workflow step executes [4][5][6]. When these expressions contain untrusted, user-controlled input (such as issue titles, pull request bodies, or labels), the content is substituted directly into the shell script being generated [1][4][7]. An attacker can craft malicious input that breaks out of any surrounding quotes or command structures to execute arbitrary shell commands on the runner [1][5][6]. Key Security Findings: - Untrusted Contexts: Any data originating from an external source (e.g., github.event.issue.title, github.event.pull_request.head_ref) should be treated as untrusted and potentially malicious [1][8]. - Preprocessing Behavior: The shell script is generated with the interpolated values already present. Consequently, standard shell-level escaping or quoting within the run block is often insufficient to prevent injection because the malicious payload is already part of the command structure before the shell even begins execution [4][5]. - Impact: Successfully exploiting this vulnerability allows an attacker to execute code with the permissions of the workflow runner, potentially exfiltrating sensitive secrets, repository tokens, or modifying the repository [5][3][7]. Recommended Mitigation: The industry-standard best practice to prevent this vulnerability is to never use ${{ }} directly inside run commands [2][4]. Instead, map the untrusted input to an intermediate environment variable and reference that variable using the native syntax of the shell (e.g., $VAR) [2][9][3]. Secure Pattern Example: # ❌ VULNERABLE - name: Process input run: echo "User input: ${{ github.event.issue.title }}" # ✅ SECURE - name: Process input env: ISSUE_TITLE: ${{ github.event.issue.title }} run: echo "User input: $ISSUE_TITLE" Additionally, consider using custom actions (e.g., JavaScript or Docker-based actions) to process sensitive input, as these pass values as arguments rather than substituting them into shell scripts, inherently avoiding this type of injection [9][8][10]. Enabling code scanning tools like CodeQL is also highly recommended to automatically identify these vulnerable patterns in your workflows [2][7].

Citations:


🌐 Web query:

GitHub Actions inputs property escape quoting in shell run block security

💡 Result:

Using GitHub Actions inputs or other user-controlled context in a shell run block creates a severe risk of shell command injection [1][2][3]. This occurs because GitHub Actions evaluates expressions inside ${{ }} syntax and substitutes them directly into the shell script before it is executed [2][4]. An attacker providing malicious input can escape the intended command string and execute arbitrary code with the workflow's permissions [2][4][3]. To secure your workflows, follow these defensive practices: 1. Use Environment Variables for Indirection: Never use the ${{ }} expression syntax directly in a run block for untrusted input [1][5]. Instead, assign the input to an environment variable in the env section of the step, and reference that variable inside the shell script using native shell syntax (e.g., "$VAR" in Bash) [1][5][4][3]. Because the expression engine never touches the shell command string, the input is treated as a literal value rather than executable code [5][3]. Example of safe usage: - env: USER_INPUT: ${{ github.event.inputs.user_input }} run: | echo "$USER_INPUT" 2. Quote Variables and Use Delimiters: Always wrap environment variables in double quotes to prevent word splitting [1][6]. Additionally, when passing inputs as arguments to commands, use the end-of-options marker (--) where supported (e.g., git diff -- "$VAR") to prevent the input from being misinterpreted as a command-line option (argument injection) [6]. 3. Validate and Sanitize Inputs: Treat all user-controlled data (e.g., pull request titles, issue bodies, workflow inputs) as untrusted [1][2]. Whenever possible, validate inputs against an allowlist (e.g., regex pattern matching) before using them [4][3][6]. 4. Limit Token Permissions: Reduce the blast radius of a potential compromise by using granular permissions for the GITHUB_TOKEN [5][4]. Avoid granting broad write or administrative access if the job only requires read-only operations [5]. By adopting these patterns, you treat workflow inputs as data rather than code, which is the standard defensive posture for preventing injection vulnerabilities [2][5].

Citations:


Avoid ${{ }} interpolation directly in shell command construction (Line 133).

Line 133 injects workflow inputs directly into the shell command via ${{ inputs.production && '--prod' || '' }} and ${{ inputs.deploy-path }}. GitHub evaluates template expressions before shell execution; a malicious input can break out of quotes and inject arbitrary commands. Move caller inputs to the env section and reference them with native shell syntax ($VAR) instead.

Suggested hardening diff
       - name: Deploy to Vercel
         id: deploy
         shell: bash --noprofile --norc -euo pipefail {0}
         env:
           VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
           VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
           VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
+          DEPLOY_PATH: ${{ inputs.deploy-path }}
+          IS_PRODUCTION: ${{ inputs.production }}
         run: |
           # `vercel deploy` prints the bare deployment URL to stdout and the
           # human-readable "Preview:"/"Inspect:" lines to stderr. Capture all of
           # it (tee back to the log so the URLs stay visible there) and parse the
           # URLs robustly from the combined output.
           deploy_log="$(mktemp)"
-          "$VERCEL_BIN" deploy --prebuilt ${{ inputs.production && '--prod' || '' }} --token="$VERCEL_TOKEN" "${{ inputs.deploy-path }}" 2>&1 | tee "$deploy_log"
+          prod_arg=()
+          if [ "$IS_PRODUCTION" = "true" ]; then
+            prod_arg+=(--prod)
+          fi
+          "$VERCEL_BIN" deploy --prebuilt "${prod_arg[@]}" --token="$VERCEL_TOKEN" "$DEPLOY_PATH" 2>&1 | tee "$deploy_log"
🧰 Tools
🪛 zizmor (1.25.2)

[error] 133-133: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 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/rainix-vercel.yaml around lines 127 - 133, The workflow
step injects user inputs directly into the shell command using GitHub template
expression syntax (${{ inputs.production && '--prod' || '' }} and ${{
inputs.deploy-path }}), which allows malicious inputs to break out of quotes and
inject arbitrary commands. Add an env section to the step that captures these
inputs as environment variables, then replace the dangerous template expressions
in the shell command with safe shell variable references using native syntax
(e.g., $PRODUCTION_FLAG and $DEPLOY_PATH). This ensures inputs are safely passed
through environment variables rather than being evaluated as part of the command
string construction.

Source: Linters/SAST tools

@thedavidmeister thedavidmeister merged commit 94a674a into main Jun 14, 2026
13 checks passed
@github-actions

Copy link
Copy Markdown

@coderabbitai assess this PR size classification for the totality of the PR with the following criterias and report it in your comment:

S/M/L PR Classification Guidelines:

This guide helps classify merged pull requests by effort and complexity rather than just line count. The goal is to assess the difficulty and scope of changes after they have been completed.

Small (S)

Characteristics:

  • Simple bug fixes, typos, or minor refactoring
  • Single-purpose changes affecting 1-2 files
  • Documentation updates
  • Configuration tweaks
  • Changes that require minimal context to review

Review Effort: Would have taken 5-10 minutes

Examples:

  • Fix typo in variable name
  • Update README with new instructions
  • Adjust configuration values
  • Simple one-line bug fixes
  • Import statement cleanup

Medium (M)

Characteristics:

  • Feature additions or enhancements
  • Refactoring that touches multiple files but maintains existing behavior
  • Breaking changes with backward compatibility
  • Changes requiring some domain knowledge to review

Review Effort: Would have taken 15-30 minutes

Examples:

  • Add new feature or component
  • Refactor common utility functions
  • Update dependencies with minor breaking changes
  • Add new component with tests
  • Performance optimizations
  • More complex bug fixes

Large (L)

Characteristics:

  • Major feature implementations
  • Breaking changes or API redesigns
  • Complex refactoring across multiple modules
  • New architectural patterns or significant design changes
  • Changes requiring deep context and multiple review rounds

Review Effort: Would have taken 45+ minutes

Examples:

  • Complete new feature with frontend/backend changes
  • Protocol upgrades or breaking changes
  • Major architectural refactoring
  • Framework or technology upgrades

Additional Factors to Consider

When deciding between sizes, also consider:

  • Test coverage impact: More comprehensive test changes lean toward larger classification
  • Risk level: Changes to critical systems bump up a size category
  • Team familiarity: Novel patterns or technologies increase complexity

Notes:

  • the assessment must be for the totality of the PR, that means comparing the base branch to the last commit of the PR
  • the assessment output must be exactly one of: S, M or L (single-line comment) in format of: SIZE={S/M/L}
  • do not include any additional text, only the size classification
  • your assessment comment must not include tips or additional sections
  • do NOT tag me or anyone else on your comment

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

SIZE=S

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.

1 participant