-
Notifications
You must be signed in to change notification settings - Fork 652
Add lint and format auto-fix workflow to automatically commit fixes on PR failures #7542
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,145 @@ | ||||||||||||
| name: Lint and Format Auto-fix | ||||||||||||
|
|
||||||||||||
| on: | ||||||||||||
| workflow_run: | ||||||||||||
| workflows: [CI] | ||||||||||||
| types: | ||||||||||||
| - completed | ||||||||||||
|
|
||||||||||||
| concurrency: | ||||||||||||
| group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }} | ||||||||||||
| cancel-in-progress: true | ||||||||||||
|
|
||||||||||||
| permissions: | ||||||||||||
| contents: write | ||||||||||||
| pull-requests: write | ||||||||||||
|
|
||||||||||||
| jobs: | ||||||||||||
| autofix: | ||||||||||||
| # Only run on pull requests where lint or format failed | ||||||||||||
| if: > | ||||||||||||
| github.event.workflow_run.event == 'pull_request' && | ||||||||||||
| github.event.workflow_run.conclusion == 'failure' | ||||||||||||
| runs-on: ubuntu-latest | ||||||||||||
| steps: | ||||||||||||
| - name: Generate token | ||||||||||||
| id: generate_token | ||||||||||||
| uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf | ||||||||||||
| with: | ||||||||||||
| app-id: ${{ secrets.APP_ID }} | ||||||||||||
| private-key: ${{ secrets.PRIVATE_KEY }} | ||||||||||||
|
|
||||||||||||
| - name: Get Pull Request | ||||||||||||
| id: pr | ||||||||||||
| uses: actions/github-script@5c56fde4671bc2d3592fb0f2c5b5bab9ddae03b1 | ||||||||||||
| with: | ||||||||||||
| script: | | ||||||||||||
| const response = await github.rest.repos.listPullRequestsAssociatedWithCommit({ | ||||||||||||
| owner: context.repo.owner, | ||||||||||||
| repo: context.repo.repo, | ||||||||||||
| commit_sha: '${{ github.event.workflow_run.head_sha }}' | ||||||||||||
| }); | ||||||||||||
|
|
||||||||||||
| if (response.data.length === 0) { | ||||||||||||
| core.info('No pull request found for this commit'); | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| const pr = response.data[0]; | ||||||||||||
| core.setOutput('number', pr.number); | ||||||||||||
| core.setOutput('head_ref', pr.head.ref); | ||||||||||||
| core.setOutput('head_repo', pr.head.repo?.full_name || ''); | ||||||||||||
|
|
||||||||||||
| - name: Check which jobs failed | ||||||||||||
| id: check_jobs | ||||||||||||
| uses: actions/github-script@5c56fde4671bc2d3592fb0f2c5b5bab9ddae03b1 | ||||||||||||
| with: | ||||||||||||
| script: | | ||||||||||||
| const jobs = await github.rest.actions.listJobsForWorkflowRun({ | ||||||||||||
| owner: context.repo.owner, | ||||||||||||
| repo: context.repo.repo, | ||||||||||||
| run_id: ${{ github.event.workflow_run.id }} | ||||||||||||
| }); | ||||||||||||
|
|
||||||||||||
| const lintJob = jobs.data.jobs.find(job => job.name === 'lint'); | ||||||||||||
| const formatJob = jobs.data.jobs.find(job => job.name === 'format'); | ||||||||||||
|
|
||||||||||||
| const lintFailed = lintJob && lintJob.conclusion === 'failure'; | ||||||||||||
| const formatFailed = formatJob && formatJob.conclusion === 'failure'; | ||||||||||||
|
|
||||||||||||
| core.setOutput('lint_failed', lintFailed ? 'true' : 'false'); | ||||||||||||
| core.setOutput('format_failed', formatFailed ? 'true' : 'false'); | ||||||||||||
| core.setOutput('should_fix', (lintFailed || formatFailed) ? 'true' : 'false'); | ||||||||||||
|
|
||||||||||||
| if (lintFailed) { | ||||||||||||
| core.info('Lint job failed, will attempt auto-fix'); | ||||||||||||
| } | ||||||||||||
| if (formatFailed) { | ||||||||||||
| core.info('Format job failed, will attempt auto-fix'); | ||||||||||||
| } | ||||||||||||
| if (!lintFailed && !formatFailed) { | ||||||||||||
| core.info('Neither lint nor format jobs failed'); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| - name: Checkout PR branch | ||||||||||||
| if: steps.check_jobs.outputs.should_fix == 'true' && steps.pr.outputs.head_ref != '' | ||||||||||||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | ||||||||||||
| with: | ||||||||||||
| repository: ${{ steps.pr.outputs.head_repo }} | ||||||||||||
| ref: ${{ steps.pr.outputs.head_ref }} | ||||||||||||
| token: ${{ steps.generate_token.outputs.token }} | ||||||||||||
| fetch-depth: 0 | ||||||||||||
|
|
||||||||||||
| - name: Set up Node.js | ||||||||||||
| if: steps.check_jobs.outputs.should_fix == 'true' && steps.pr.outputs.head_ref != '' | ||||||||||||
| uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f | ||||||||||||
| with: | ||||||||||||
| node-version-file: '.nvmrc' | ||||||||||||
| cache: 'npm' | ||||||||||||
|
|
||||||||||||
| - name: Install dependencies | ||||||||||||
| if: steps.check_jobs.outputs.should_fix == 'true' && steps.pr.outputs.head_ref != '' | ||||||||||||
| run: npm ci | ||||||||||||
|
|
||||||||||||
| - name: Run format auto-fix | ||||||||||||
| if: steps.check_jobs.outputs.format_failed == 'true' && steps.pr.outputs.head_ref != '' | ||||||||||||
| run: npm run format | ||||||||||||
|
|
||||||||||||
| - name: Run lint auto-fix | ||||||||||||
| if: steps.check_jobs.outputs.lint_failed == 'true' && steps.pr.outputs.head_ref != '' | ||||||||||||
| run: | | ||||||||||||
|
||||||||||||
| run: | | |
| run: | | |
| # Note: CI runs `npm run lint:md` for markdown linting, but markdownlint-cli2 | |
| # does not currently support auto-fix. If a markdown lint auto-fix command | |
| # (e.g. `npm run lint:md:fix`) is added in the future, update this step to run it. |
Copilot
AI
Feb 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workflow will create a PR comment even when no fixes were actually committed. The git-auto-commit-action by default doesn't fail or skip when there are no changes to commit - it just doesn't create a commit. However, the "Comment on PR" step will still run and post a misleading message saying issues were fixed.
To fix this, you can capture the output from the git-auto-commit-action step (it sets a changes_detected output) and only run the comment step when changes were actually committed. For example:
- Add an
idto the "Commit and push changes" step - Update the "Comment on PR" condition to also check
steps.commit_step_id.outputs.changes_detected == 'true'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workflow will fail when attempting to auto-fix lint/format issues on pull requests from forks. When a PR comes from a fork, the GitHub App token won't have permission to push to the fork repository. The workflow should check if the PR is from a fork and skip execution in that case, similar to how other workflows in this repository handle forks (e.g., deploy_preview.yml checks
github.event.pull_request.head.repo.full_name == 'primer/react').Add a check in the job-level condition to skip fork PRs. You can check this by comparing the head repo with the base repo, or by adding a check in the "Get Pull Request" step to set an output indicating whether it's from a fork, then use that in subsequent step conditions.