feat(repo): add GitHub Actions workflow to validate PR titles - #1
feat(repo): add GitHub Actions workflow to validate PR titles#1x56uwnhoff wants to merge 1 commit into
Conversation
Adds a new PR check that validates PR titles match our git commit rules using the existing commit-lint.js script. The workflow runs on PR events and ensures all PR titles follow the conventional commit format. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe pull request adds a GitHub Actions workflow that runs ChangesPull request title validation
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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/pr-title-validation.yml:
- Around line 29-33: Update the pull-request title validation flow around the
heredoc writing /tmp/pr-message.txt so it validates only
github.event.pull_request.title, excluding the pull-request body. Preserve the
existing commit-lint.js invocation while ensuring invalid title suffixes or
valid-looking lines in the body cannot satisfy the title check.
- Around line 9-13: Add workflow-level least-privilege permissions in the PR
title validation workflow by declaring contents read access near the workflow
configuration, before the jobs section. Keep the existing validate-pr-title job
unchanged.
- Around line 3-7: Remove either the pull_request or pull_request_target trigger
from the workflow, keeping only one event configuration with its existing
activity types to ensure pull request title validation runs once.
- Around line 15-19: Update the actions/checkout@v4 step in the workflow to set
persist-credentials to false alongside the existing ref configuration,
preventing the checkout action from storing the repository token in Git
configuration.
🪄 Autofix
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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 99438903-f8fc-4daf-a4c5-1644365ee303
📒 Files selected for processing (1)
.github/workflows/pr-title-validation.yml
| on: | ||
| pull_request: | ||
| types: [opened, edited, synchronize, reopened] | ||
| pull_request_target: | ||
| types: [opened, edited, synchronize, reopened] |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- workflow ---'
cat -n .github/workflows/pr-title-validation.yml
printf '%s\n' '--- workflow files and trigger references ---'
git ls-files '.github/workflows/*'
rg -n --glob '.github/workflows/*' 'pull_request_target|pull_request:|concurrency:|workflow_run|pr-title|title' .github/workflowsRepository: LLM-CR-EVAL/nx-242-cr1
Length of output: 3489
🌐 Web query:
GitHub Actions workflow both pull_request and pull_request_target same event two workflow runs documentation
💡 Result:
Yes, you can configure a single GitHub Actions workflow to be triggered by both pull_request and pull_request_target events [1][2]. You can include both in the on block of your workflow file [1]. When you define a workflow to listen for both events, a single pull request activity (such as opening or synchronizing a PR) can trigger two separate workflow runs: one for pull_request and one for pull_request_target [3][2]. Key differences to consider when using them together: 1. Context and Security: - pull_request: Runs the workflow code from the pull request's merge commit [4][3]. It is restricted to a read-only GITHUB_TOKEN and does not have access to repository secrets to prevent unauthorized code execution from forks [4][5]. - pull_request_target: Runs the workflow code from the target repository's base branch (the version of the code that is trusted) [4][3]. This allows the workflow to access repository secrets and a read/write GITHUB_TOKEN, making it suitable for tasks like labeling or commenting on PRs [3][2]. 2. Triggering: - Both events support the same activity types (such as opened, synchronize, and reopened) [6][2]. - If a pull request has a merge conflict, pull_request workflows will not run, but pull_request_target workflows will still trigger [6][2]. Because these two events run in different contexts, you should be careful if you use them in the same workflow. It is common practice to use conditional logic (e.g., if: github.event_name == 'pull_request') within your jobs or steps if you need to perform different actions depending on which event triggered the specific run [7]. Additionally, exercise extreme caution when using pull_request_target, as it executes code from your base branch while handling potentially untrusted data from a pull request [4][2]. Top results: [6][4][1][2]
Citations:
- 1: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
- 2: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- 3: https://stackoverflow.com/questions/74957218/what-is-the-difference-between-pull-request-and-pull-request-target-event-in-git
- 4: https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target
- 5: https://docs.github.com/en/enterprise-cloud@latest/actions/reference/security/securely-using-pull_request_target
- 6: https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows
- 7: https://docs.github.com/en/actions/reference/workflows-and-actions/variables
Keep only one pull request trigger.
The matching pull request event starts one run for pull_request and another for pull_request_target. Remove one trigger to prevent duplicate validation runs and check results.
🧰 Tools
🪛 zizmor (1.29.0)
[error] 3-7: use of fundamentally insecure workflow trigger (dangerous-triggers): pull_request_target is almost always used insecurely
(dangerous-triggers)
🤖 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-title-validation.yml around lines 3 - 7, Remove either
the pull_request or pull_request_target trigger from the workflow, keeping only
one event configuration with its existing activity types to ensure pull request
title validation runs once.
| jobs: | ||
| validate-pr-title: | ||
| if: ${{ github.repository_owner == 'nrwl' }} | ||
| name: Validate PR Title | ||
| runs-on: ubuntu-latest |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
file=".github/workflows/pr-title-validation.yml"
printf '%s\n' '--- workflow ---'
cat -n "$file"
printf '%s\n' '--- permission and checkout references ---'
rg -n '^\s*permissions:|^\s+(contents|actions|pull-requests|statuses|checks):|actions/checkout|github\.token|gh |curl |git push|git commit' "$file" || true
printf '%s\n' '--- bounded structural check ---'
python3 - <<'PY'
from pathlib import Path
p = Path(".github/workflows/pr-title-validation.yml")
text = p.read_text()
lines = text.splitlines()
permissions = [i + 1 for i, line in enumerate(lines) if line.strip() == "permissions:"]
checkout = [i + 1 for i, line in enumerate(lines) if "actions/checkout" in line]
print({"permissions_lines": permissions, "checkout_lines": checkout})
PYRepository: LLM-CR-EVAL/nx-242-cr1
Length of output: 1691
Security Misconfiguration (CWE-732): Incorrect Permission Assignment for Critical Resource
Reachability: External
Declare least-privilege permissions.
Add workflow-level permissions: contents: read. The workflow currently inherits repository or organization defaults, although it only needs read access to repository contents.
🤖 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-title-validation.yml around lines 9 - 13, Add
workflow-level least-privilege permissions in the PR title validation workflow
by declaring contents read access near the workflow configuration, before the
jobs section. Keep the existing validate-pr-title job unchanged.
Source: Linters/SAST tools
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| # For pull_request_target, we need to checkout the base branch | ||
| ref: ${{ github.event.pull_request.base.ref }} |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows/pr-title-validation.yml' '.github/workflows/*' | sed -n '1,80p'
printf '%s\n' '--- target workflow ---'
cat -n .github/workflows/pr-title-validation.yml
printf '%s\n' '--- checkout and shell-related references ---'
rg -n -C 4 'actions/checkout|pull_request_target|run:|github\.event|commit-lint|commit message|title' .github/workflows/pr-title-validation.ymlRepository: LLM-CR-EVAL/nx-242-cr1
Length of output: 3232
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
workflow = Path(".github/workflows/pr-title-validation.yml").read_text()
start = workflow.index(" - name: Create PR message file")
end = workflow.index("\n - name: Validate PR title", start)
block = workflow[start:end]
print("--- workflow block ---")
print(block)
# Model GitHub expression interpolation into the run block. The payload is
# data supplied by the pull request, not repository code.
title = 'fix(core): valid $(cat "$GITHUB_WORKSPACE/.git/config")'
body = 'safe\nEOF\nprintf "injected-command-ran\\n"'
generated = block.replace("${{ github.event.pull_request.title }}", title)
generated = generated.replace("${{ github.event.pull_request.body }}", body)
print("--- generated run content ---")
print(generated)
print("--- credential persistence indicators ---")
checkout = workflow[workflow.index("uses: actions/checkout@v4"):workflow.index("\n\n", workflow.index("uses: actions/checkout@v4"))]
print(checkout)
print("persist-credentials explicitly disabled:", "persist-credentials: false" in checkout)
PYRepository: LLM-CR-EVAL/nx-242-cr1
Length of output: 889
🌐 Web query:
actions/checkout v4 persist-credentials default true local git config official documentation
💡 Result:
In actions/checkout v4, the persist-credentials input defaults to true [1][2][3]. When persist-credentials is set to true (the default), the action configures the authentication token or SSH key to enable authenticated Git commands for subsequent steps in the workflow [1][4]. Official documentation and technical implementation details confirm that this configuration is managed through the local Git configuration [1][2]. Regarding how these credentials are stored: - In actions/checkout v4, the authentication token is persisted directly into the repository's .git/config file [5]. - Note that in later versions (e.g., v6 and beyond), the implementation was improved to store credentials in a separate file within $RUNNER_TEMP, with the .git/config file only containing a pointer to that location [6][7][5]. This change prevents credentials from being included if the workspace directory is uploaded as a build artifact [5]. If you do not require authenticated Git commands in subsequent steps, you can set persist-credentials: false to opt out and prevent the credentials from being written to the configuration [1][5]. Sources: [1] https://github.com/actions/checkout/blob/v4/README.md [2] https://github.com/actions/checkout/blob/v4/action.yml [3] https://github.com/actions/checkout/blob/v4.1.1/action.yml [6] https://github.com/actions/checkout/tree/v6.0.2?tab=readme-ov-file [7] https://github.com/actions/checkout/tree/v6.0.0 [5] cli/go-gh#225
Citations:
- 1: https://github.com/actions/checkout/blob/v4/README.md
- 2: https://github.com/actions/checkout/blob/v4/action.yml
- 3: https://github.com/actions/checkout/blob/v4.1.1/action.yml
- 4: https://github.com/actions/checkout
- 5: Set
persist-credentials: falseon checkout steps cli/go-gh#225 - 6: https://github.com/actions/checkout/tree/v6.0.2?tab=readme-ov-file
- 7: https://github.com/actions/checkout/tree/v6.0.0
Sensitive Data Exposure (CWE-522): Insufficiently Protected Credentials
Reachability: External · Exploitability: Trivial
Disable checkout credential persistence.
The workflow interpolates the pull request title and body into shell commands. An attacker can use this injection to read the token that actions/checkout@v4 stores in .git/config by default. Set persist-credentials: false.
🧰 Tools
🪛 zizmor (1.29.0)
[warning] 15-19: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 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-title-validation.yml around lines 15 - 19, Update the
actions/checkout@v4 step in the workflow to set persist-credentials to false
alongside the existing ref configuration, preventing the checkout action from
storing the repository token in Git configuration.
Source: Linters/SAST tools
| cat > /tmp/pr-message.txt << 'EOF' | ||
| ${{ github.event.pull_request.title }} | ||
|
|
||
| ${{ github.event.pull_request.body }} | ||
| EOF |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Validate the title independently of the body.
The supplied scripts/commit-lint.js, Lines 1-83, tests an unanchored regular expression against the entire file. This workflow appends the body at Line 32. An invalid title can pass when the body contains a valid type(scope): subject line. A title with a valid prefix and invalid suffix can also pass.
Write only the title for a title check, or anchor the validator to the complete first line.
🧰 Tools
🪛 zizmor (1.29.0)
[error] 30-30: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 32-32: 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/pr-title-validation.yml around lines 29 - 33, Update the
pull-request title validation flow around the heredoc writing
/tmp/pr-message.txt so it validates only github.event.pull_request.title,
excluding the pull-request body. Preserve the existing commit-lint.js invocation
while ensuring invalid title suffixes or valid-looking lines in the body cannot
satisfy the title check.
Current Behavior
Currently, there is no automated check to ensure that PR titles follow our conventional commit format. This can lead to inconsistent PR titles that don't match our commit conventions.
Expected Behavior
With this change, all PR titles will be automatically validated against our git commit rules when a PR is:
The check uses the existing
scripts/commit-lint.jsscript to validate PR titles, ensuring they follow the same format as our commit messages (e.g.,feat(scope): description,fix(scope): description, etc.).Related Issue(s)
This was requested to improve PR quality and consistency across the repository.
Implementation Details
.github/workflows/pr-title-validation.ymlnode ./scripts/commit-lint.json the file to validate the PR titlenrwlrepository owner (consistent with other workflows)Test Plan
feat(core): add new feature)fix(core)!: breaking change)🤖 Generated with Claude Code
Summary by CodeRabbit