From f72dbbb7430185203ddeb608624e167cb58812de Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Fri, 16 Jan 2026 16:57:04 -0600 Subject: [PATCH 01/15] Add lint action and workflow --- .github/template-files/config.yml | 7 +- lint/README.md | 117 ++++++++++++++ lint/action.yml | 251 ++++++++++++++++++++++++++++++ lint/workflow.yml.tmpl | 51 ++++++ 4 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 lint/README.md create mode 100644 lint/action.yml create mode 100644 lint/workflow.yml.tmpl diff --git a/.github/template-files/config.yml b/.github/template-files/config.yml index 9cfaa93f..9f11716c 100644 --- a/.github/template-files/config.yml +++ b/.github/template-files/config.yml @@ -7,7 +7,7 @@ conda/infrastructure: - .github/workflows/cla.yml - .github/workflows/update.yml - # [optional] to include repo in https://github.com/orgs/conda/projects/2 + # [optional] project management workflows - .github/workflows/issues.yml - .github/workflows/labels.yml - .github/workflows/project.yml @@ -48,3 +48,8 @@ conda/infrastructure: # dst: rever.xsh # codespell:ignore rever # - src: templates/releases/TEMPLATE # dst: news/TEMPLATE + +conda/actions: + # [optional] lint workflow (requires .pre-commit-config.yaml) + - src: lint/workflow.yml.tmpl + dst: .github/workflows/lint.yml diff --git a/lint/README.md b/lint/README.md new file mode 100644 index 00000000..b50e7141 --- /dev/null +++ b/lint/README.md @@ -0,0 +1,117 @@ +# Lint Action + +A composite GitHub Action that runs [prek](https://github.com/j178/prek) (a fast pre-commit hook runner) with optional autofix and PR comments. + +## Files + +- `action.yml` - Composite action with all logic +- `workflow.yml.tmpl` - Workflow template for syncing to repos + +## Features + +- Installs and runs prek with your existing `.pre-commit-config.yaml` +- Captures command output and git diff for PR comments +- Creates/updates sticky PR comments showing lint issues and suggested fixes +- Updates comment to show success when issues are resolved +- Optionally commits and pushes fixes (autofix mode) +- Reacts to trigger comments with 👀 → 🎉/😕 + +## Usage + +### Basic Usage (lint check only) + +```yaml +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: conda/actions/lint@main +``` + +The action automatically fails if lint issues are found (unless `autofix: true`). + +### With Autofix via Comment Trigger + +```yaml +on: + pull_request: + issue_comment: + types: [created] + +jobs: + lint: + if: >- + github.event_name == 'pull_request' + || ( + github.event_name == 'issue_comment' + && github.event.issue.pull_request + && github.event.comment.body == '@conda-bot prek autofix' + ) + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: conda/actions/lint@main + with: + autofix: ${{ github.event_name == 'issue_comment' }} + comment-id: ${{ github.event.comment.id }} + pr-number: ${{ github.event.issue.number }} +``` + +## Inputs + +| Input | Description | Required | Default | +|-------|-------------|----------|---------| +| `token` | GitHub token for PR comments and pushing | No | `${{ github.token }}` | +| `autofix` | Whether to commit and push fixes | No | `'false'` | +| `comment-id` | Comment ID to react to (for issue_comment triggers) | No | `''` | +| `pr-number` | PR number (defaults to current PR, override for issue_comment triggers) | No | `${{ github.event.pull_request.number }}` | +| `git-user-name` | Git user name for autofix commits | No | `conda-bot` | +| `git-user-email` | Git user email for autofix commits | No | `18747875+conda-bot@users.noreply.github.com` | +| `python-version` | Python version for running prek hooks | No | `'3.12'` | + +## Outputs + +| Output | Description | +|--------|-------------| +| `outcome` | `success` if no lint issues, `failure` if issues found | +| `output` | The prek command output | +| `diff` | The git diff of suggested fixes (only if outcome is failure) | + +## Behavior by Event Type + +### `push` + +- Runs prek on the pushed branch +- Fails if lint issues found (no PR comment - no PR context) + +### `pull_request` + +- Runs prek on the PR +- On failure: creates/updates PR comment with issues and diff +- On success (after previous failure): updates comment to "✅ Lint issues fixed" +- Detects fork PRs and shows a note that autofix won't work + +### `issue_comment` (with `autofix: true`) + +- Reacts to trigger comment with 👀 +- Checks out PR branch via `gh pr checkout` +- Runs prek +- On success (fixes pushed): reacts 🎉, updates comment to "✅ Lint issues fixed" +- On push failure (fork): reacts 😕, updates comment with warning +- On no issues: reacts 🎉 + +## PR Comments + +The action creates a sticky comment (identified by ``) that: + +- Shows prek output and git diff on failure +- Shows a note for fork PRs (autofix cannot push to forks) +- Shows a warning if autofix was attempted but push failed +- Updates to "✅ Lint issues fixed" when resolved +- Includes link to workflow run for details + +## Limitations + +- **Fork PRs**: The default `GITHUB_TOKEN` cannot push to forks. Autofix will fail on fork PRs with a clear message explaining how to fix locally. A GitHub App with broader permissions could enable this in the future. diff --git a/lint/action.yml b/lint/action.yml new file mode 100644 index 00000000..43de5091 --- /dev/null +++ b/lint/action.yml @@ -0,0 +1,251 @@ +name: Lint +description: Run prek/pre-commit hooks with optional autofix and PR comments. +inputs: + token: + description: GitHub token for PR comments and pushing + default: ${{ github.token }} + autofix: + description: Whether to commit and push fixes + default: 'false' + comment-id: + description: Comment ID to react to (for issue_comment triggers) + default: '' + pr-number: + description: PR number (defaults to current PR, override for issue_comment triggers) + default: ${{ github.event.pull_request.number }} + git-user-name: + description: Git user name for autofix commits + default: conda-bot + git-user-email: + description: Git user email for autofix commits + default: 18747875+conda-bot@users.noreply.github.com + python-version: + description: Python version for running prek hooks + default: '3.12' +outputs: + outcome: + description: "'success' if no lint issues, 'failure' if issues found" + value: ${{ steps.prek.outputs.outcome }} + output: + description: The prek command output + value: ${{ steps.prek.outputs.output }} + diff: + description: The git diff of suggested fixes (only if outcome is failure) + value: ${{ steps.diff.outputs.diff }} + +runs: + using: composite + steps: + # Validate that pr-number is set when comment-id is provided + - name: Validate inputs + if: inputs.comment-id && !inputs.pr-number + shell: bash + run: | + echo "::error::pr-number is required when comment-id is provided" + exit 1 + + # React with eyes to indicate processing (autofix only) + - if: inputs.comment-id + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 + with: + token: ${{ inputs.token }} + comment-id: ${{ inputs.comment-id }} + reactions: eyes + reactions-edit-mode: replace + + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + # Checkout PR branch for autofix (gh handles fork remotes automatically) + - if: inputs.pr-number + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + run: gh pr checkout ${{ inputs.pr-number }} + + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ inputs.python-version }} + + # install-only so we can capture prek output + - uses: j178/prek-action@9d6a3097e0c1865ecce00cfb89fe80f2ee91b547 # v1.0.12 + with: + install-only: true + + - name: Run prek + id: prek + shell: bash + run: | + { + echo 'output<&1 && exit_code=0 || exit_code=$? + echo 'EOF' + } >> "$GITHUB_OUTPUT" + # fail if prek exited non-zero OR made changes + if [[ $exit_code -ne 0 ]] || [[ -n "$(git status --porcelain)" ]]; then + echo "outcome=failure" >> "$GITHUB_OUTPUT" + else + echo "outcome=success" >> "$GITHUB_OUTPUT" + fi + + - name: Capture diff + id: diff + if: steps.prek.outputs.outcome == 'failure' + shell: bash + run: | + { + echo 'diff<> "$GITHUB_OUTPUT" + + # Find existing lint comment (skip on push events - no PR context) + - name: Find existing lint comment + if: inputs.pr-number + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0 + id: lint-comment + with: + token: ${{ inputs.token }} + issue-number: ${{ inputs.pr-number }} + body-includes: + + # Commit and push fixes (autofix only) + - name: Commit and push fixes + id: push + if: steps.prek.outputs.outcome == 'failure' && inputs.autofix == 'true' + continue-on-error: true + shell: bash + run: | + git config user.name '${{ inputs.git-user-name }}' + git config user.email '${{ inputs.git-user-email }}' + git add -A + git commit --message 'style: auto-fix lint issues' + git push + + # Reset changes if not autofix mode, or if autofix push failed + - name: Reset changes + if: steps.prek.outputs.outcome == 'failure' && (inputs.autofix != 'true' || steps.push.outcome == 'failure') + shell: bash + run: git checkout -- . + + # React to command comment on successful autofix + - name: React on successful autofix + if: steps.push.outcome == 'success' && inputs.comment-id + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 + with: + token: ${{ inputs.token }} + comment-id: ${{ inputs.comment-id }} + reactions: hooray + reactions-edit-mode: replace + + # Update lint comment to show success (lint passed or autofix pushed) + - name: Update lint comment on success + if: (steps.prek.outputs.outcome == 'success' || steps.push.outcome == 'success') && steps.lint-comment.outputs.comment-id + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 + with: + token: ${{ inputs.token }} + comment-id: ${{ steps.lint-comment.outputs.comment-id }} + edit-mode: replace + body: | + + ## ✅ Lint issues fixed + + All lint issues have been resolved. + + # Check if this is a fork PR (autofix won't be able to push) + - name: Detect fork PR + id: fork + if: github.event_name == 'pull_request' + shell: bash + env: + IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + run: echo "is_fork=$IS_FORK" >> "$GITHUB_OUTPUT" + + # Prepare admonition for fork PRs or autofix failures + - name: Set admonition + id: error-comment + if: steps.push.outcome == 'failure' || steps.fork.outputs.is_fork == 'true' + shell: bash + env: + ADMONITION_FORK: | + > [!NOTE] + > This PR is from a fork. Autofix cannot push to forks, so please fix locally. + ADMONITION_FAILED: | + > [!WARNING] + > Autofix failed to push. This is likely because the PR is from a fork, and GitHub Actions cannot push to forks with the default token. + run: | + { + echo 'admonition<> "$GITHUB_OUTPUT" + + # Comment on PR with lint errors (after push so we know if autofix failed) + - name: Comment on PR with errors + if: steps.prek.outputs.outcome == 'failure' && (github.event_name == 'pull_request' || steps.push.outcome == 'failure') + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 + with: + token: ${{ inputs.token }} + comment-id: ${{ steps.lint-comment.outputs.comment-id }} + issue-number: ${{ inputs.pr-number }} + edit-mode: replace + body: | + + ## ⚠️ Lint issues found + + ${{ steps.error-comment.outputs.admonition }} + + prek found issues that need to be fixed. You can either: + 1. Fix them locally and push + 2. Comment `@conda-bot prek autofix` to auto-commit fixes (requires write access) + + ```bash + # install prek: https://github.com/j178/prek?tab=readme-ov-file#installation + prek run --all-files + git add -A && git commit -m "style: auto-fix lint issues" && git push + ``` + +
+ 🔍 prek output (click to expand) + + ``` + ${{ steps.prek.outputs.output }} + ``` + +
+ +
+ 📝 Suggested fixes (click to expand) + + ```diff + ${{ steps.diff.outputs.diff }} + ``` + +
+ + ###### See ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} for details. + + # React to command comment on failed autofix + - name: React on failed autofix + if: steps.push.outcome == 'failure' && inputs.comment-id + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 + with: + token: ${{ inputs.token }} + comment-id: ${{ inputs.comment-id }} + reactions: confused + reactions-edit-mode: replace + + # React to command comment if no lint issues (autofix only, no push needed) + - name: React on no issues + if: steps.prek.outputs.outcome == 'success' && inputs.comment-id + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 + with: + token: ${{ inputs.token }} + comment-id: ${{ inputs.comment-id }} + reactions: hooray + reactions-edit-mode: replace + + # Fail if lint found issues (not in autofix mode) or if autofix push failed + - name: Fail if lint found issues + if: (steps.prek.outputs.outcome == 'failure' && inputs.autofix != 'true') || steps.push.outcome == 'failure' + shell: bash + run: exit 1 diff --git a/lint/workflow.yml.tmpl b/lint/workflow.yml.tmpl new file mode 100644 index 00000000..3ca982da --- /dev/null +++ b/lint/workflow.yml.tmpl @@ -0,0 +1,51 @@ +name: Lint + +on: + # https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads#push + push: + branches: + - main +[% for branch in branches | default([]) %] + - '[[ branch ]]' +[% endfor %] + + # https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request + pull_request: + + # https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads#issue_comment + issue_comment: + types: [created] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.issue.number || github.sha }} + cancel-in-progress: true + +jobs: + lint: + # run on push/PR, or on "@conda-bot prek autofix" comment from maintainers or PR author + if: >- + !github.event.repository.fork + && ( + github.event_name == 'push' + || github.event_name == 'pull_request' + || ( + github.event_name == 'issue_comment' + && github.event.issue.pull_request + && github.event.comment.body == '@conda-bot prek autofix' + && ( + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) + || github.event.comment.user.login == github.event.issue.user.login + ) + ) + ) + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: conda/actions/lint@main + with: + autofix: ${{ github.event_name == 'issue_comment' }} + comment-id: ${{ github.event.comment.id }} + pr-number: ${{ github.event.issue.number }} + python-version: '[[ python_version | default("3.12") ]]' From 998d2f155218cfa79f19ead481a218a5a9a43836 Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 18:15:07 -0600 Subject: [PATCH 02/15] Test lint action --- .github/workflows/tests.yml | 81 ++++++++++++++++++++++++++++++- lint/README.md | 19 ++++++++ lint/action.yml | 25 ++++++++-- lint/data/.pre-commit-config.yaml | 8 +++ lint/data/clean.py | 6 +++ lint/data/with_issues.py | 10 ++++ pyproject.toml | 2 + 7 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 lint/data/.pre-commit-config.yaml create mode 100644 lint/data/clean.py create mode 100644 lint/data/with_issues.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3bbc0744..f498e59b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -131,9 +131,88 @@ jobs: ${{ steps.templates-error.outputs.summary }} GITHUB_TOKEN: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }} + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout Source + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Filter Changes + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + lint: + - 'lint/**' + + # Test 1: Clean repo should pass lint (no comment created since it passes) + - name: Run lint on clean repo + id: lint-clean + continue-on-error: true + uses: ./lint + with: + token: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }} + checkout: false + # Only comment on PR if lint code changed + pr-number: ${{ steps.filter.outputs.lint == 'true' && github.event.pull_request.number || '' }} + comment-anchor: lint-comment-test-clean + comment-header: | + > [!WARNING] + > This comment should not appear - clean repos should not produce lint comments. + + # Test 2: File with issues should fail lint (using test data directory) + - name: Run lint on test data with issues + id: lint-issues + continue-on-error: true + uses: ./lint + with: + token: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }} + checkout: false + # Only comment on PR if lint code changed + pr-number: ${{ steps.filter.outputs.lint == 'true' && github.event.pull_request.number || '' }} + working-directory: lint/data + comment-anchor: lint-comment-test-issues + comment-header: | + > [!WARNING] + > This is what the lint comment looks like when there are lint issues. + + - name: Reset test data changes + run: git checkout -- lint/data/ + + # Verify all test outcomes + - name: Verify test outcomes + run: | + failed=0 + + # Test 1: Clean repo should pass + if [[ "${{ steps.lint-clean.outputs.outcome }}" != "success" ]]; then + echo "::error::Test 1: Expected outcome=success, got ${{ steps.lint-clean.outputs.outcome }}" + failed=1 + fi + if [[ -z "${{ steps.lint-clean.outputs.output }}" ]]; then + echo "::error::Test 1: Expected output to be non-empty" + failed=1 + fi + + # Test 2: Issues should fail + if [[ "${{ steps.lint-issues.outputs.outcome }}" != "failure" ]]; then + echo "::error::Test 2: Expected outcome=failure, got ${{ steps.lint-issues.outputs.outcome }}" + failed=1 + fi + if ! echo "${{ steps.lint-issues.outputs.output }}" | grep -q "F401"; then + echo "::error::Test 2: Expected output to contain F401 (unused import)" + failed=1 + fi + if [[ -z "${{ steps.lint-issues.outputs.diff }}" ]]; then + echo "::error::Test 2: Expected diff output to be non-empty" + failed=1 + fi + + exit $failed + # required check analyze: - needs: [pytest, read-file, template-files] + needs: [pytest, read-file, template-files, lint] if: '!cancelled()' runs-on: ubuntu-latest steps: diff --git a/lint/README.md b/lint/README.md index b50e7141..15f51ab8 100644 --- a/lint/README.md +++ b/lint/README.md @@ -70,6 +70,10 @@ jobs: | `git-user-name` | Git user name for autofix commits | No | `conda-bot` | | `git-user-email` | Git user email for autofix commits | No | `18747875+conda-bot@users.noreply.github.com` | | `python-version` | Python version for running prek hooks | No | `'3.12'` | +| `checkout` | Whether to checkout the repository (set to false if already checked out) | No | `'true'` | +| `working-directory` | Directory to run prek in (defaults to repo root) | No | `'.'` | +| `comment-anchor` | Unique anchor for sticky comment (customize to avoid conflicts with parallel workflows) | No | `'lint-comment'` | +| `comment-header` | Optional header text to prepend to comments (e.g., to mark test comments) | No | `''` | ## Outputs @@ -79,6 +83,21 @@ jobs: | `output` | The prek command output | | `diff` | The git diff of suggested fixes (only if outcome is failure) | +## Disabling PR Comments + +To run lint without creating PR comments, omit the `pr-number` input: + +```yaml +- uses: conda/actions/lint@main + with: + pr-number: '' # No PR comments +``` + +This is useful for: +- Running lint in contexts without a PR (e.g., scheduled runs) +- CI test scenarios where you don't want test comments cluttering PRs +- Conditional commenting based on file changes (see tests.yml for an example) + ## Behavior by Event Type ### `push` diff --git a/lint/action.yml b/lint/action.yml index 43de5091..7203c840 100644 --- a/lint/action.yml +++ b/lint/action.yml @@ -22,6 +22,18 @@ inputs: python-version: description: Python version for running prek hooks default: '3.12' + checkout: + description: Whether to checkout the repository (set to false if already checked out) + default: 'true' + working-directory: + description: Directory to run prek in (defaults to repo root) + default: . + comment-anchor: + description: Unique anchor for sticky comment (customize to avoid conflicts with parallel workflows) + default: lint-comment + comment-header: + description: Optional header text to prepend to comments (e.g., to mark test comments) + default: '' outputs: outcome: description: "'success' if no lint issues, 'failure' if issues found" @@ -53,7 +65,8 @@ runs: reactions: eyes reactions-edit-mode: replace - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - if: inputs.checkout == 'true' + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 # Checkout PR branch for autofix (gh handles fork remotes automatically) - if: inputs.pr-number @@ -74,6 +87,7 @@ runs: - name: Run prek id: prek shell: bash + working-directory: ${{ inputs.working-directory }} run: | { echo 'output< + body-includes: # Commit and push fixes (autofix only) - name: Commit and push fixes @@ -146,7 +161,8 @@ runs: comment-id: ${{ steps.lint-comment.outputs.comment-id }} edit-mode: replace body: | - + + ${{ inputs.comment-header }} ## ✅ Lint issues fixed All lint issues have been resolved. @@ -189,7 +205,8 @@ runs: issue-number: ${{ inputs.pr-number }} edit-mode: replace body: | - + + ${{ inputs.comment-header }} ## ⚠️ Lint issues found ${{ steps.error-comment.outputs.admonition }} diff --git a/lint/data/.pre-commit-config.yaml b/lint/data/.pre-commit-config.yaml new file mode 100644 index 00000000..dfe48e0f --- /dev/null +++ b/lint/data/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +# Minimal pre-commit config for testing +# This mirrors what repos using the lint action would have +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.0 + hooks: + - id: ruff-check + args: [--fix] diff --git a/lint/data/clean.py b/lint/data/clean.py new file mode 100644 index 00000000..36767122 --- /dev/null +++ b/lint/data/clean.py @@ -0,0 +1,6 @@ +# Test file that passes lint +# This file is used by the test workflow to verify the lint action passes clean files + + +def hello(): + print("hello") diff --git a/lint/data/with_issues.py b/lint/data/with_issues.py new file mode 100644 index 00000000..566b0cc1 --- /dev/null +++ b/lint/data/with_issues.py @@ -0,0 +1,10 @@ +# Test file with intentional lint issues (unused imports) +# NOTE: This file is excluded from the repo's pre-commit via pyproject.toml +# It's used by the test workflow to verify the lint action detects issues + +import os +import sys + + +def hello(): + print("hello") diff --git a/pyproject.toml b/pyproject.toml index 2acdb683..7fbd18ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,8 @@ addopts = [ ] [tool.ruff] +# exclude test files with intentional lint issues +exclude = ["lint/data/with_issues.py"] show-fixes = true target-version = "py310" From b18ff9c8152ef0ebb280676a43d1eca402a11149 Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 18:23:24 -0600 Subject: [PATCH 03/15] Bump versions --- lint/action.yml | 2 +- lint/data/.pre-commit-config.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lint/action.yml b/lint/action.yml index 7203c840..7365de01 100644 --- a/lint/action.yml +++ b/lint/action.yml @@ -116,7 +116,7 @@ runs: # Find existing lint comment (skip on push events - no PR context) - name: Find existing lint comment if: inputs.pr-number - uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0 + uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0 id: lint-comment with: token: ${{ inputs.token }} diff --git a/lint/data/.pre-commit-config.yaml b/lint/data/.pre-commit-config.yaml index dfe48e0f..2da6f145 100644 --- a/lint/data/.pre-commit-config.yaml +++ b/lint/data/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # This mirrors what repos using the lint action would have repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.0 + rev: v0.14.11 hooks: - id: ruff-check args: [--fix] From 25fab4ab26afe931785e6a8cc6a30f580ecb2af5 Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 18:32:01 -0600 Subject: [PATCH 04/15] Add minimal test pyproject.toml --- lint/data/pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 lint/data/pyproject.toml diff --git a/lint/data/pyproject.toml b/lint/data/pyproject.toml new file mode 100644 index 00000000..bce59daf --- /dev/null +++ b/lint/data/pyproject.toml @@ -0,0 +1,5 @@ +# Minimal ruff config for testing lint action +# This file stops ruff from using the parent pyproject.toml + +[tool.ruff.lint] +select = ["F"] # pyflakes, includes F401 (unused-import) From a4e6eb4a34d2003ab0c7f407635b8c217bb06c5a Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 19:15:38 -0600 Subject: [PATCH 05/15] Update .pre-commit-config.yaml --- .pre-commit-config.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f4a82b2..7fcfab6b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,6 @@ +# exclude test files with intentional lint issues +exclude: ^lint/data/with_issues\.py$ + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 From 6896c8c94fd42f4daf11d13dcff063dd16587d3c Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 19:22:09 -0600 Subject: [PATCH 06/15] Isolate test cases --- .github/workflows/tests.yml | 45 ++++++++++--------- .pre-commit-config.yaml | 2 +- lint/data/clean.py | 6 --- lint/data/{ => error}/.pre-commit-config.yaml | 3 +- lint/data/error/example.py | 7 +++ lint/data/error/pyproject.toml | 3 ++ lint/data/pyproject.toml | 5 --- lint/data/success/.pre-commit-config.yaml | 7 +++ lint/data/success/example.py | 5 +++ lint/data/success/pyproject.toml | 4 ++ lint/data/with_issues.py | 10 ----- pyproject.toml | 2 +- 12 files changed, 52 insertions(+), 47 deletions(-) delete mode 100644 lint/data/clean.py rename lint/data/{ => error}/.pre-commit-config.yaml (57%) create mode 100644 lint/data/error/example.py create mode 100644 lint/data/error/pyproject.toml delete mode 100644 lint/data/pyproject.toml create mode 100644 lint/data/success/.pre-commit-config.yaml create mode 100644 lint/data/success/example.py create mode 100644 lint/data/success/pyproject.toml delete mode 100644 lint/data/with_issues.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f498e59b..f2d20316 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -145,33 +145,34 @@ jobs: lint: - 'lint/**' - # Test 1: Clean repo should pass lint (no comment created since it passes) - - name: Run lint on clean repo - id: lint-clean + # Test 1: Success case - clean code should pass lint + - name: Run lint on success test data + id: lint-success continue-on-error: true uses: ./lint with: token: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }} checkout: false + working-directory: lint/data/success # Only comment on PR if lint code changed pr-number: ${{ steps.filter.outputs.lint == 'true' && github.event.pull_request.number || '' }} - comment-anchor: lint-comment-test-clean + comment-anchor: lint-comment-test-success comment-header: | > [!WARNING] - > This comment should not appear - clean repos should not produce lint comments. + > This comment should not appear - success test should not produce lint comments. - # Test 2: File with issues should fail lint (using test data directory) - - name: Run lint on test data with issues - id: lint-issues + # Test 2: Error case - code with issues should fail lint + - name: Run lint on error test data + id: lint-error continue-on-error: true uses: ./lint with: token: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }} checkout: false + working-directory: lint/data/error # Only comment on PR if lint code changed pr-number: ${{ steps.filter.outputs.lint == 'true' && github.event.pull_request.number || '' }} - working-directory: lint/data - comment-anchor: lint-comment-test-issues + comment-anchor: lint-comment-test-error comment-header: | > [!WARNING] > This is what the lint comment looks like when there are lint issues. @@ -184,27 +185,27 @@ jobs: run: | failed=0 - # Test 1: Clean repo should pass - if [[ "${{ steps.lint-clean.outputs.outcome }}" != "success" ]]; then - echo "::error::Test 1: Expected outcome=success, got ${{ steps.lint-clean.outputs.outcome }}" + # Test 1: Success case should pass + if [[ "${{ steps.lint-success.outputs.outcome }}" != "success" ]]; then + echo "::error::Test 1 (success): Expected outcome=success, got ${{ steps.lint-success.outputs.outcome }}" failed=1 fi - if [[ -z "${{ steps.lint-clean.outputs.output }}" ]]; then - echo "::error::Test 1: Expected output to be non-empty" + if [[ -z "${{ steps.lint-success.outputs.output }}" ]]; then + echo "::error::Test 1 (success): Expected output to be non-empty" failed=1 fi - # Test 2: Issues should fail - if [[ "${{ steps.lint-issues.outputs.outcome }}" != "failure" ]]; then - echo "::error::Test 2: Expected outcome=failure, got ${{ steps.lint-issues.outputs.outcome }}" + # Test 2: Error case should fail + if [[ "${{ steps.lint-error.outputs.outcome }}" != "failure" ]]; then + echo "::error::Test 2 (error): Expected outcome=failure, got ${{ steps.lint-error.outputs.outcome }}" failed=1 fi - if ! echo "${{ steps.lint-issues.outputs.output }}" | grep -q "F401"; then - echo "::error::Test 2: Expected output to contain F401 (unused import)" + if ! echo "${{ steps.lint-error.outputs.output }}" | grep -q "F401"; then + echo "::error::Test 2 (error): Expected output to contain F401 (unused import)" failed=1 fi - if [[ -z "${{ steps.lint-issues.outputs.diff }}" ]]; then - echo "::error::Test 2: Expected diff output to be non-empty" + if [[ -z "${{ steps.lint-error.outputs.diff }}" ]]; then + echo "::error::Test 2 (error): Expected diff output to be non-empty" failed=1 fi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7fcfab6b..2f779b6e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ # exclude test files with intentional lint issues -exclude: ^lint/data/with_issues\.py$ +exclude: ^lint/data/error/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/lint/data/clean.py b/lint/data/clean.py deleted file mode 100644 index 36767122..00000000 --- a/lint/data/clean.py +++ /dev/null @@ -1,6 +0,0 @@ -# Test file that passes lint -# This file is used by the test workflow to verify the lint action passes clean files - - -def hello(): - print("hello") diff --git a/lint/data/.pre-commit-config.yaml b/lint/data/error/.pre-commit-config.yaml similarity index 57% rename from lint/data/.pre-commit-config.yaml rename to lint/data/error/.pre-commit-config.yaml index 2da6f145..5811d65c 100644 --- a/lint/data/.pre-commit-config.yaml +++ b/lint/data/error/.pre-commit-config.yaml @@ -1,5 +1,4 @@ -# Minimal pre-commit config for testing -# This mirrors what repos using the lint action would have +# Minimal pre-commit config for testing lint action (error case) repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.14.11 diff --git a/lint/data/error/example.py b/lint/data/error/example.py new file mode 100644 index 00000000..7bf344fd --- /dev/null +++ b/lint/data/error/example.py @@ -0,0 +1,7 @@ +# File with intentional lint issues (unused imports) +import os +import sys + + +def hello(): + print("hello") diff --git a/lint/data/error/pyproject.toml b/lint/data/error/pyproject.toml new file mode 100644 index 00000000..4e527849 --- /dev/null +++ b/lint/data/error/pyproject.toml @@ -0,0 +1,3 @@ +# Minimal ruff config for testing lint action (error case) +[tool.ruff.lint] +select = ["F"] # pyflakes, includes F401 (unused-import) diff --git a/lint/data/pyproject.toml b/lint/data/pyproject.toml deleted file mode 100644 index bce59daf..00000000 --- a/lint/data/pyproject.toml +++ /dev/null @@ -1,5 +0,0 @@ -# Minimal ruff config for testing lint action -# This file stops ruff from using the parent pyproject.toml - -[tool.ruff.lint] -select = ["F"] # pyflakes, includes F401 (unused-import) diff --git a/lint/data/success/.pre-commit-config.yaml b/lint/data/success/.pre-commit-config.yaml new file mode 100644 index 00000000..f7d11d4c --- /dev/null +++ b/lint/data/success/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +# Minimal pre-commit config for testing lint action (success case) +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.11 + hooks: + - id: ruff-check + args: [--fix] diff --git a/lint/data/success/example.py b/lint/data/success/example.py new file mode 100644 index 00000000..2f481d3e --- /dev/null +++ b/lint/data/success/example.py @@ -0,0 +1,5 @@ +# Clean file with no lint issues + + +def hello(): + print("hello") diff --git a/lint/data/success/pyproject.toml b/lint/data/success/pyproject.toml new file mode 100644 index 00000000..84cdb120 --- /dev/null +++ b/lint/data/success/pyproject.toml @@ -0,0 +1,4 @@ +# Minimal ruff config for testing lint action (success case) + +[tool.ruff.lint] +select = ["F"] # pyflakes diff --git a/lint/data/with_issues.py b/lint/data/with_issues.py deleted file mode 100644 index 566b0cc1..00000000 --- a/lint/data/with_issues.py +++ /dev/null @@ -1,10 +0,0 @@ -# Test file with intentional lint issues (unused imports) -# NOTE: This file is excluded from the repo's pre-commit via pyproject.toml -# It's used by the test workflow to verify the lint action detects issues - -import os -import sys - - -def hello(): - print("hello") diff --git a/pyproject.toml b/pyproject.toml index 7fbd18ba..05c8e8f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ addopts = [ [tool.ruff] # exclude test files with intentional lint issues -exclude = ["lint/data/with_issues.py"] +exclude = ["lint/data/error/"] show-fixes = true target-version = "py310" From e379962146cb9f2d6ff8b9c093c103cb0875d36d Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 19:27:26 -0600 Subject: [PATCH 07/15] Show ruff fixes --- lint/data/error/.pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint/data/error/.pre-commit-config.yaml b/lint/data/error/.pre-commit-config.yaml index 5811d65c..ca10f44a 100644 --- a/lint/data/error/.pre-commit-config.yaml +++ b/lint/data/error/.pre-commit-config.yaml @@ -4,4 +4,4 @@ repos: rev: v0.14.11 hooks: - id: ruff-check - args: [--fix] + args: [--fix, --show-fixes] From 6a6b0926d0f36e93943505326736530f56039f76 Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 19:36:48 -0600 Subject: [PATCH 08/15] Add config input for pre-commit config file path Introduces a new 'config' input to specify the path to the pre-commit config file in the lint action. Updates documentation and workflow to support this option, and renames test config files for consistency. --- .github/workflows/tests.yml | 2 ++ lint/README.md | 1 + lint/action.yml | 7 ++++++- .../{.pre-commit-config.yaml => pre-commit-config.yaml} | 0 .../{.pre-commit-config.yaml => pre-commit-config.yaml} | 0 5 files changed, 9 insertions(+), 1 deletion(-) rename lint/data/error/{.pre-commit-config.yaml => pre-commit-config.yaml} (100%) rename lint/data/success/{.pre-commit-config.yaml => pre-commit-config.yaml} (100%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f2d20316..c64d865d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -154,6 +154,7 @@ jobs: token: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }} checkout: false working-directory: lint/data/success + config: pre-commit-config.yaml # Only comment on PR if lint code changed pr-number: ${{ steps.filter.outputs.lint == 'true' && github.event.pull_request.number || '' }} comment-anchor: lint-comment-test-success @@ -170,6 +171,7 @@ jobs: token: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }} checkout: false working-directory: lint/data/error + config: pre-commit-config.yaml # Only comment on PR if lint code changed pr-number: ${{ steps.filter.outputs.lint == 'true' && github.event.pull_request.number || '' }} comment-anchor: lint-comment-test-error diff --git a/lint/README.md b/lint/README.md index 15f51ab8..8d203e44 100644 --- a/lint/README.md +++ b/lint/README.md @@ -72,6 +72,7 @@ jobs: | `python-version` | Python version for running prek hooks | No | `'3.12'` | | `checkout` | Whether to checkout the repository (set to false if already checked out) | No | `'true'` | | `working-directory` | Directory to run prek in (defaults to repo root) | No | `'.'` | +| `config` | Path to pre-commit config file (defaults to auto-discovery) | No | `''` | | `comment-anchor` | Unique anchor for sticky comment (customize to avoid conflicts with parallel workflows) | No | `'lint-comment'` | | `comment-header` | Optional header text to prepend to comments (e.g., to mark test comments) | No | `''` | diff --git a/lint/action.yml b/lint/action.yml index 7365de01..a918157f 100644 --- a/lint/action.yml +++ b/lint/action.yml @@ -28,6 +28,9 @@ inputs: working-directory: description: Directory to run prek in (defaults to repo root) default: . + config: + description: Path to pre-commit config file (defaults to auto-discovery) + default: '' comment-anchor: description: Unique anchor for sticky comment (customize to avoid conflicts with parallel workflows) default: lint-comment @@ -88,10 +91,12 @@ runs: id: prek shell: bash working-directory: ${{ inputs.working-directory }} + env: + CONFIG_ARG: ${{ inputs.config && format('--config {0}', inputs.config) || '' }} run: | { echo 'output<&1 && exit_code=0 || exit_code=$? + prek run --all-files --color never $CONFIG_ARG 2>&1 && exit_code=0 || exit_code=$? echo 'EOF' } >> "$GITHUB_OUTPUT" # fail if prek exited non-zero OR made changes diff --git a/lint/data/error/.pre-commit-config.yaml b/lint/data/error/pre-commit-config.yaml similarity index 100% rename from lint/data/error/.pre-commit-config.yaml rename to lint/data/error/pre-commit-config.yaml diff --git a/lint/data/success/.pre-commit-config.yaml b/lint/data/success/pre-commit-config.yaml similarity index 100% rename from lint/data/success/.pre-commit-config.yaml rename to lint/data/success/pre-commit-config.yaml From 9aa175ea35dfd771c717e765d11426ce63adc49b Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 19:42:04 -0600 Subject: [PATCH 09/15] Inline fork detection --- lint/action.yml | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/lint/action.yml b/lint/action.yml index a918157f..8030b907 100644 --- a/lint/action.yml +++ b/lint/action.yml @@ -172,27 +172,18 @@ runs: All lint issues have been resolved. - # Check if this is a fork PR (autofix won't be able to push) - - name: Detect fork PR - id: fork - if: github.event_name == 'pull_request' - shell: bash - env: - IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }} - run: echo "is_fork=$IS_FORK" >> "$GITHUB_OUTPUT" - - # Prepare admonition for fork PRs or autofix failures + # Prepare admonition for autofix failures or fork PRs - name: Set admonition id: error-comment - if: steps.push.outcome == 'failure' || steps.fork.outputs.is_fork == 'true' + if: steps.push.outcome == 'failure' || github.event.pull_request.head.repo.full_name != github.repository shell: bash env: - ADMONITION_FORK: | - > [!NOTE] - > This PR is from a fork. Autofix cannot push to forks, so please fix locally. ADMONITION_FAILED: | > [!WARNING] > Autofix failed to push. This is likely because the PR is from a fork, and GitHub Actions cannot push to forks with the default token. + ADMONITION_FORK: | + > [!NOTE] + > This PR is from a fork. Autofix cannot push to forks, so please fix locally. run: | { echo 'admonition< Date: Tue, 20 Jan 2026 19:53:17 -0600 Subject: [PATCH 10/15] Emphasize working-directory --- .github/workflows/tests.yml | 10 ++++++++-- lint/action.yml | 8 ++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c64d865d..c56bc208 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -150,16 +150,19 @@ jobs: id: lint-success continue-on-error: true uses: ./lint + env: + WORKING_DIRECTORY: lint/data/success with: token: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }} checkout: false - working-directory: lint/data/success + working-directory: ${{ env.WORKING_DIRECTORY }} config: pre-commit-config.yaml # Only comment on PR if lint code changed pr-number: ${{ steps.filter.outputs.lint == 'true' && github.event.pull_request.number || '' }} comment-anchor: lint-comment-test-success comment-header: | > [!WARNING] + > **Working directory:** `${{ env.WORKING_DIRECTORY }}` > This comment should not appear - success test should not produce lint comments. # Test 2: Error case - code with issues should fail lint @@ -167,16 +170,19 @@ jobs: id: lint-error continue-on-error: true uses: ./lint + env: + WORKING_DIRECTORY: lint/data/error with: token: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }} checkout: false - working-directory: lint/data/error + working-directory: ${{ env.WORKING_DIRECTORY }} config: pre-commit-config.yaml # Only comment on PR if lint code changed pr-number: ${{ steps.filter.outputs.lint == 'true' && github.event.pull_request.number || '' }} comment-anchor: lint-comment-test-error comment-header: | > [!WARNING] + > **Working directory:** `${{ env.WORKING_DIRECTORY }}` > This is what the lint comment looks like when there are lint issues. - name: Reset test data changes diff --git a/lint/action.yml b/lint/action.yml index 8030b907..8341c254 100644 --- a/lint/action.yml +++ b/lint/action.yml @@ -172,9 +172,9 @@ runs: All lint issues have been resolved. - # Prepare admonition for autofix failures or fork PRs - - name: Set admonition - id: error-comment + # Admonition for autofix failures or fork PRs + - name: Set fork/failure admonition + id: admonition if: steps.push.outcome == 'failure' || github.event.pull_request.head.repo.full_name != github.repository shell: bash env: @@ -205,7 +205,7 @@ runs: ${{ inputs.comment-header }} ## ⚠️ Lint issues found - ${{ steps.error-comment.outputs.admonition }} + ${{ steps.admonition.outputs.admonition }} prek found issues that need to be fixed. You can either: 1. Fix them locally and push From acd6faa17da5f66c603bc6e9b8e496d23bd9588f Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 19:58:04 -0600 Subject: [PATCH 11/15] Limit prek scope --- lint/data/error/pre-commit-config.yaml | 1 + lint/data/success/pre-commit-config.yaml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lint/data/error/pre-commit-config.yaml b/lint/data/error/pre-commit-config.yaml index ca10f44a..d324f987 100644 --- a/lint/data/error/pre-commit-config.yaml +++ b/lint/data/error/pre-commit-config.yaml @@ -1,4 +1,5 @@ # Minimal pre-commit config for testing lint action (error case) +files: ^lint/data/error/ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.14.11 diff --git a/lint/data/success/pre-commit-config.yaml b/lint/data/success/pre-commit-config.yaml index f7d11d4c..89285400 100644 --- a/lint/data/success/pre-commit-config.yaml +++ b/lint/data/success/pre-commit-config.yaml @@ -1,7 +1,8 @@ # Minimal pre-commit config for testing lint action (success case) +files: ^lint/data/success/ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.14.11 hooks: - id: ruff-check - args: [--fix] + args: [--fix, --show-fixes] From 796008ed8cdaac44170cc4433782fad025b6386d Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 20:15:18 -0600 Subject: [PATCH 12/15] Add comment-on-success option to lint action Introduces a new 'comment-on-success' input to the lint GitHub Action, allowing a success comment to be posted even if there were no prior lint failures. Updates documentation and workflow to support this feature, which is useful for testing and visibility. --- .github/workflows/tests.yml | 3 ++- lint/README.md | 1 + lint/action.yml | 8 +++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c56bc208..7c3f0376 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -160,10 +160,11 @@ jobs: # Only comment on PR if lint code changed pr-number: ${{ steps.filter.outputs.lint == 'true' && github.event.pull_request.number || '' }} comment-anchor: lint-comment-test-success + comment-on-success: ${{ steps.filter.outputs.lint == 'true' }} comment-header: | > [!WARNING] > **Working directory:** `${{ env.WORKING_DIRECTORY }}` - > This comment should not appear - success test should not produce lint comments. + > This is what the lint comment looks like when there are no lint issues. # Test 2: Error case - code with issues should fail lint - name: Run lint on error test data diff --git a/lint/README.md b/lint/README.md index 8d203e44..45f3ccd5 100644 --- a/lint/README.md +++ b/lint/README.md @@ -75,6 +75,7 @@ jobs: | `config` | Path to pre-commit config file (defaults to auto-discovery) | No | `''` | | `comment-anchor` | Unique anchor for sticky comment (customize to avoid conflicts with parallel workflows) | No | `'lint-comment'` | | `comment-header` | Optional header text to prepend to comments (e.g., to mark test comments) | No | `''` | +| `comment-on-success` | Create success comment even without prior lint failure (useful for testing) | No | `'false'` | ## Outputs diff --git a/lint/action.yml b/lint/action.yml index 8341c254..04655906 100644 --- a/lint/action.yml +++ b/lint/action.yml @@ -37,6 +37,9 @@ inputs: comment-header: description: Optional header text to prepend to comments (e.g., to mark test comments) default: '' + comment-on-success: + description: Create success comment even without prior lint failure (useful for testing) + default: 'false' outputs: outcome: description: "'success' if no lint issues, 'failure' if issues found" @@ -159,11 +162,12 @@ runs: # Update lint comment to show success (lint passed or autofix pushed) - name: Update lint comment on success - if: (steps.prek.outputs.outcome == 'success' || steps.push.outcome == 'success') && steps.lint-comment.outputs.comment-id + if: (steps.prek.outputs.outcome == 'success' || steps.push.outcome == 'success') && (steps.lint-comment.outputs.comment-id || inputs.comment-on-success == 'true') uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 with: token: ${{ inputs.token }} comment-id: ${{ steps.lint-comment.outputs.comment-id }} + issue-number: ${{ inputs.pr-number }} edit-mode: replace body: | @@ -172,6 +176,8 @@ runs: All lint issues have been resolved. + ###### See ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} for details. + # Admonition for autofix failures or fork PRs - name: Set fork/failure admonition id: admonition From 33c93086274fe7c27f754f4cb04bf8d1d875f2ec Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 20:38:32 -0600 Subject: [PATCH 13/15] Document how to circumventing fork limitation --- lint/README.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/lint/README.md b/lint/README.md index 45f3ccd5..6ce08875 100644 --- a/lint/README.md +++ b/lint/README.md @@ -133,6 +133,27 @@ The action creates a sticky comment (identified by ``) that - Updates to "✅ Lint issues fixed" when resolved - Includes link to workflow run for details -## Limitations +## Fork PRs -- **Fork PRs**: The default `GITHUB_TOKEN` cannot push to forks. Autofix will fail on fork PRs with a clear message explaining how to fix locally. A GitHub App with broader permissions could enable this in the future. +By default, `GITHUB_TOKEN` cannot push to fork PRs. The action detects this and shows a helpful message explaining how to fix locally. + +To enable autofix for fork PRs, use a GitHub App token instead (**untested**): + +```yaml +- uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + +- uses: conda/actions/lint@main + with: + token: ${{ steps.app-token.outputs.token }} + autofix: true + comment-id: ${{ github.event.comment.id }} + pr-number: ${{ github.event.issue.number }} +``` + +Requirements: +- GitHub App with `contents: write` and `pull-requests: write` permissions +- PR author must enable "Allow edits from maintainers" From eff4fa4825b9e65f38d15a5f51f6d0b3ad18f945 Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 20:43:50 -0600 Subject: [PATCH 14/15] Update README.md --- lint/README.md | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/lint/README.md b/lint/README.md index 6ce08875..58a44278 100644 --- a/lint/README.md +++ b/lint/README.md @@ -100,29 +100,6 @@ This is useful for: - CI test scenarios where you don't want test comments cluttering PRs - Conditional commenting based on file changes (see tests.yml for an example) -## Behavior by Event Type - -### `push` - -- Runs prek on the pushed branch -- Fails if lint issues found (no PR comment - no PR context) - -### `pull_request` - -- Runs prek on the PR -- On failure: creates/updates PR comment with issues and diff -- On success (after previous failure): updates comment to "✅ Lint issues fixed" -- Detects fork PRs and shows a note that autofix won't work - -### `issue_comment` (with `autofix: true`) - -- Reacts to trigger comment with 👀 -- Checks out PR branch via `gh pr checkout` -- Runs prek -- On success (fixes pushed): reacts 🎉, updates comment to "✅ Lint issues fixed" -- On push failure (fork): reacts 😕, updates comment with warning -- On no issues: reacts 🎉 - ## PR Comments The action creates a sticky comment (identified by ``) that: From f3eb4cdb4bdf0a1b53bc54ce78da19354d1fecab Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 20 Jan 2026 20:45:03 -0600 Subject: [PATCH 15/15] Update README.md --- lint/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lint/README.md b/lint/README.md index 58a44278..00d55747 100644 --- a/lint/README.md +++ b/lint/README.md @@ -7,6 +7,20 @@ A composite GitHub Action that runs [prek](https://github.com/j178/prek) (a fast - `action.yml` - Composite action with all logic - `workflow.yml.tmpl` - Workflow template for syncing to repos +## Syncing to Repositories + +To adopt this workflow via template-files, add to your `.github/template-files/config.yml`: + +```yaml +- source: lint/workflow.yml.tmpl + target: .github/workflows/lint.yml + # Optional: additional branch patterns (main is always included) + # branches: + # - '2[0-9].[0-9]+.x' # CalVer release branches + # Optional: Python version for prek hooks + # python_version: '3.12' +``` + ## Features - Installs and runs prek with your existing `.pre-commit-config.yaml`