github

This guide explains how to set up Lintro with GitHub Actions for automated code quality checks, coverage reporting, and CI/CD integration.

GitHub Integration Guide

This guide explains how to set up Lintro with GitHub Actions for automated code quality checks, coverage reporting, and CI/CD integration.

Quick Setup

The repository includes pre-configured GitHub Actions workflows. To activate them:

  1. Enable GitHub Pages in repository settings (for coverage badges)
  2. Push to main branch to trigger workflows
  3. Add badges to your README.md (optional)

Available Workflows

1. Quality Check Workflow

File: .github/workflows/docker-ci.yml (dogfooding-quality job)

Delegates to lgtm-ci reusable-quality.yml, linting with the Docker image built in the same workflow run (lintro-image: ghcr.io/lgtm-hq/py-lintro:ci-<run_id>). Posts results as a PR comment.

Triggers: Pull requests, pushes to main, merge queue, manual dispatch (via docker-ci).

2. Test Suite & Coverage

File: .github/workflows/test-ci.yml and .github/workflows/docker-ci.yml

Features:

  • 🧪 Unit test coverage via lgtm-ci reusable-test-python.yml (Python 3.11 + 3.14)
  • 🐳 Docker integration tests in docker-ci.yml

4. Lintro Report Workflow

File: .github/workflows/lintro-report-scheduled.yml

Features:

  • 📊 Comprehensive codebase analysis with Lintro
  • 📈 Report generation in multiple formats (Grid, Markdown)
  • 📋 GitHub Actions summary with detailed results
  • 📦 Artifact upload for report retention
  • 🌐 Optional GitHub Pages deployment for report hosting

If you want to publish the weekly report to Pages, prefer using a dedicated deploy-pages job gated on the report workflow.

4b. AI Review (Dogfood) Workflow

File: .github/workflows/ai-review.yml

py-lintro dogfoods its own lintro review command on pull requests that touch lintro/**. The workflow runs an AI diff review and prints the JSON result to the job log.

Features:

  • 🤖 AI diff review via lintro review --pr <n> --depth 1 --output json
  • 🛡️ Trusted install — lintro is installed from the PR’s base ref (main, via pull_request.base.sha), never the PR head. The code that runs with the credential is always trusted, so a PR cannot substitute its own lintro/** to exfiltrate it. The PR is still reviewed: lintro review --pr fetches the diff through gh (GitHub API), so the PR’s changes are reviewed as data and never executed with the secret. Trade-off: a PR that breaks the review code itself isn’t caught by this job — that is covered by the unit tests.
  • 🔑 Bring-your-own credential — runs the cli transport against the pinned claude CLI, authenticated by the CLAUDE_CODE_OAUTH_TOKEN secret (a Claude subscription session). ANTHROPIC_API_KEY is deliberately not in scope, and LINTRO_CLI_BARE: never keeps --bare off the command line so the OAuth session is actually used (#1838). The CLI version is pinned in docker/ai-tools.Dockerfile and installed from npm at that exact version.
  • 💸 Bounded spend (advisory under the CLI transport)ai.max_cost_usd comes from the trusted base config, so a PR cannot raise the cap. It prices only the tokens lintro billed itself, so on the cli transport — where the call bills the subscription — it bounds lintro’s own accounting rather than enforcing spend. Setting a cap does not serialize provider calls: the budget checks and charges the ceiling around each call rather than holding a lock across it, so budgeted chunk reviews still run concurrently. The trade-off is that calls already in flight when the ceiling is reached still finish, so the final total can overshoot ai.max_cost_usd by roughly one round of concurrent calls.
  • 🟡 Loud but non-blocking — the check is deliberately not required, but it is not unconditionally green either: it reddens whenever no review was produced (missing or dead credential, depleted balance, unreachable provider, lintro-side failure). See the No-Silent-Skip note in #1826 — a continue-on-error job rewrites its conclusion to success, which is how “AI Review ✓” meant nothing for months.
  • ⏭️ Skipped, not failed, where it cannot run — draft PRs and fork PRs (which cannot read secrets) never start the job at all.

To activate it, add a CLAUDE_CODE_OAUTH_TOKEN secret to the repository or organization (Settings → Secrets and variables → Actions). Mint one with claude setup-token. Because reviews run using trusted base-branch lintro, the token is safe to enable.

Activation precondition (security audit #1317)

Before enabling the provider credential, confirm the dogfood workflow still satisfies all three controls (also asserted in tests/scripts/test_run_ai_review.py):

  1. Same-repo only — the job if guard requires pull_request.head.repo.full_name == github.repository (fork PRs never run).
  2. Trusted install — the checkout step uses pull_request.base.sha, never the PR head, so code that runs with the credential is always from the trusted base ref. The claude CLI is installed at a version pinned in that same trusted checkout.
  3. Secret orderingCLAUDE_CODE_OAUTH_TOKEN is injected only into the final review step’s env, after checkout, the CLI install, and dependency install.

These controls landed with #1074; #1317 verified them against current main. A dedicated GitHub Environment with required reviewers is optional once (1–3) hold. Re-run the audit if the checkout ref, job guard, or secret injection site changes.

JSON error contract

Under --output json, a provider failure (invalid key, rate limit, depleted quota/credits, 5xx, or a malformed model response) emits a stable machine-readable error envelope on stdout and exits with code 2, so CI consumers can classify failures without scraping human-readable stderr prose:

{
  "error": {
    "kind": "auth_failed",
    "provider": "anthropic",
    "status": 401,
    "retryable": false,
    "message": "Anthropic authentication failed: Error code: 401 - authentication_error"
  }
}
FieldTypeMeaning
kindstring (enum)Canonical classification (see below). Stable across providers.
providerstringProvider identifier, lowercased (e.g. anthropic, openai, cursor).
statusinteger | nullExtracted HTTP status (e.g. 401, 429, 529), or null when none.
retryablebooleantrue for transient conditions safe to retry unchanged.
messagestringThe most specific underlying cause text.

kind is one of: auth_failed, insufficient_credits, quota_exceeded, rate_limited, context_length, server_error, timeout, invalid_response, unknown. retryable is true only for rate_limited, server_error, and timeout.

Exit codes under --output json:

CodeMeaning
0Review completed, no P1 findings. Success envelope on stdout.
1Review completed with P1 findings. Success envelope on stdout.
2Provider/execution failure. Error envelope (above) on stdout.

Exit 2 disambiguates a provider error from the P1-findings exit 1, so consumers never have to guess whether stdout holds a review or an error — check for the top-level error key.

5. Docker Image Publishing

File: .github/workflows/docker-build-publish.yml

Features:

  • 🐳 Automated Docker image building and publishing to GHCR
  • 🏷️ Smart tagging - Latest, main branch, and semantic versions
  • 🔄 Release integration - Images published on releases
  • 📦 GHCR integration — Full image at ghcr.io/lgtm-hq/py-lintro
  • 📦 GHCR base image — Minimal image at ghcr.io/lgtm-hq/py-lintro-base

The full image (ghcr.io/lgtm-hq/py-lintro) includes the runtime and optional tooling so you can run Lintro out of the box. The base image (ghcr.io/lgtm-hq/py-lintro-base) is a slimmer layer with core dependencies only—use it when you want a smaller footprint, CI-only steps, or a foundation for a custom image. Add your own packages or layers on top of the base as needed.

FROM ghcr.io/lgtm-hq/py-lintro-base
# Install project-specific tools or copy your app here

7. OpenSSF Allstar (Repository Security Enforcement)

Allstar is an OpenSSF GitHub App that enforces repository security policies org-wide or per-repo. To enable at the repo level:

  • Create .allstar/ with:
    • allstar.yaml → enable opt-in at repo level
    • branch_protection.yaml, binary_artifacts.yaml, outside.yaml, security.yaml each with optConfig: { optIn: true } and action: issue as a safe default.

Install and configure via the Allstar app and docs:

  • App install: https://github.com/apps/allstar-app
  • Policies and schema: https://github.com/ossf/allstar#policies
  • Manual install guide: https://github.com/ossf/allstar/blob/main/manual-install.md

Notes:

  • Org-wide management prefers an org .allstar repository with opt-out strategy.
  • Repo-level configs require org disableRepoOverride to be false to take effect.

Usage in CI/CD:

You can use the published Docker image in your own CI/CD pipelines:

# GitHub Actions example
- name: Run Lintro with Docker
  run: |
    docker run --rm -v ${{ github.workspace }}:/code \
      ghcr.io/lgtm-hq/py-lintro:latest check --output-format grid

# GitLab CI example
lintro:
  image: ghcr.io/lgtm-hq/py-lintro:latest
  script:
    - lintro check --output-format grid

Setting Up in Your Repository

1. Copy Workflow Files

Copy the workflow files from this repository to your project:

mkdir -p .github/workflows
cp .github/workflows/*.yml your-project/.github/workflows/

2. Customize for Your Project

Edit the workflow files to match your project structure:

# .github/workflows/docker-ci.yml — dogfooding-quality calls lgtm-ci reusable-quality
# with lintro-image set to the CI-built ghcr.io/lgtm-hq/py-lintro:ci-<run_id> tag.

3. Configure Repository Settings (optional for Pages)

Enable GitHub Pages:

  1. Go to repository SettingsPages
  2. Select Source: “GitHub Actions”
  3. Your coverage badge will be available at: https://lgtm-hq.github.io/py-lintro/badges/coverage.svg

Release Automation (Single Release Train)

The repository ships with fully automated releases and PyPI publishing via lgtm-ci reusable workflows.

  • Automated Release PR (.github/workflows/release-version-pr.yml)

    • On push to main, computes the next version from Conventional Commits
    • Updates version files via lgtm-ci ecosystem updaters
    • Opens a Release PR (no direct push to main) with auto-merge enabled
  • Auto Tag on Main (.github/workflows/release-auto-tag.yml)

    • After the Release PR is merged, creates/pushes the version tag
    • GitHub Release is created by publish-pypi-on-tag.yml on tag push
  • Publish to PyPI on Tag (.github/workflows/publish-pypi-on-tag.yml)

    • On tag push (e.g., 1.2.3), verifies tag equals pyproject.toml version
    • Uses Trusted Publishing (OIDC) to upload to PyPI
    • Also creates a GitHub Release and attaches built artifacts

End-to-end: Conventional commits → Release PR (auto-merged) → Tag created → PyPI publish.

Permissions Model (least privilege)

  • Default each workflow to permissions: { contents: read }.
  • Grant elevated permissions only where required:
    • Tag creation job: contents: write.
    • PyPI publish job: id-token: write (for OIDC) and contents: write only if creating a GH Release.
    • PR comment jobs: pull-requests: write.

Why we do not allow astral-sh/setup-uv

Our Actions policy requires that all actions (including transitive actions used by composites) are allowlisted and pinned to a full commit SHA. The astral-sh/setup-uv action invokes actions/setup-python@v5 internally, which is both not on our allowlist and referenced by tag (not a 40-char SHA). This causes policy enforcement to block any job that uses setup-uv.

To comply, we replaced it with an internal composite setup-env that:

  • installs uv via pip (no nested actions),
  • provisions the requested Python version via uv python install, and
  • syncs dependencies, keeping our pipeline policy-compliant.

Deprecated/manual flows (e.g., direct Release creation workflows) are removed to avoid parallel release paths.

Labels & guards

  • Release PRs are labeled release-bump to make them easy to target in policies.
  • Tagging is handled by release-auto-tag.yml, which only tags commits matching chore(release): version … after Release PR merges.

Security & Pinning

  • Third-party actions are pinned to commit SHAs for reproducibility and supply-chain safety.
  • Official GitHub actions can also be pinned; we’ve pinned most for consistency.
  • pypa/gh-action-pypi-publish remains on release/v1 by policy (Trusted Publishing updates). If desired, pinning to a SHA is possible.

Example Workflows

Basic Quality Check

name: Code Quality

on:
  pull_request:
  push:
    branches: [main]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.13'

      - name: Install UV
        run: pip install uv

      - name: Install dependencies
        run: uv sync

      - name: Run Lintro
        run: |
          # Run core tools, then post-checks (Black) per pyproject config
          uv run lintro check --output-format grid --output lintro-results.txt
          cat lintro-results.txt

      - name: Upload results
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: lintro-results
          path: lintro-results.txt

Auto-fix Pull Request

name: Auto-fix Code Issues

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  autofix:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.13'

      - name: Install UV and dependencies
        run: |
          pip install uv
          uv sync

      - name: Run Lintro auto-fix
        run: uv run lintro format --output-format grid

      - name: Check for changes
        id: verify-changed-files
        run: |
          if [ -n "$(git status --porcelain)" ]; then
            echo "changed=true" >> $GITHUB_OUTPUT
          else
            echo "changed=false" >> $GITHUB_OUTPUT
          fi

      - name: Commit changes
        if: steps.verify-changed-files.outputs.changed == 'true'
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add .
          git commit -m "style: auto-fix code issues with Lintro"
          git push

Quality Gate

name: Quality Gate

on:
  pull_request:

jobs:
  quality-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.13'

      - name: Install UV and dependencies
        run: |
          pip install uv
          uv sync

      - name: Run quality checks
        run: |
          # Try to auto-fix first
          uv run lintro format --output-format grid

          # Then check for remaining issues
          uv run lintro check --output-format grid --output quality-report.txt

          # Fail if critical issues remain
          if grep -q "error" quality-report.txt; then
            echo "❌ Critical quality issues found"
            cat quality-report.txt
            exit 1
          else
            echo "✅ Quality gate passed"
          fi

Badge Integration

Coverage Badge

Add to your README.md:

![Coverage](https://lgtm-hq.github.io/py-lintro/badges/coverage.svg)

Quality Badge

![Code Quality](https://github.com/lgtm-hq/py-lintro/workflows/CI%20-%20Quality/badge.svg)

Custom Lintro Badge

![Lintro](https://img.shields.io/badge/code%20quality-lintro-blue)

OpenSSF Scorecard Badge

Add to your README.md:

[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/lgtm-hq/py-lintro/badge)](https://scorecard.dev/viewer/?uri=github.com/lgtm-hq/py-lintro)

Reference installation docs: https://github.com/ossf/scorecard?tab=readme-ov-file#installation.

Advanced Configuration

Tool-Specific Workflows

# Python-only quality check
- name: Python Quality
  run: uv run lintro check src/ tests/ --tools ruff,pydoclint --output-format grid

# Frontend-only quality check
- name: Frontend Quality
  run: uv run lintro check assets/ --tools prettier --output-format grid

# Infrastructure quality check
- name: Infrastructure Quality
  run: uv run lintro check Dockerfile* --tools hadolint --output-format grid

Matrix Builds

strategy:
  matrix:
    python-version: ['3.11', '3.12', '3.13']
    tool: ['ruff', 'pydoclint', 'oxfmt']

Conditional Execution

- name: Run Lintro on changed files
  run: |
    # Get changed files
    git diff --name-only HEAD^ HEAD > changed-files.txt

    # Run Lintro only on changed files
    if [ -s changed-files.txt ]; then
      uv run lintro check $(cat changed-files.txt) --output-format grid
    else
      echo "No files changed"
    fi

Troubleshooting

Common Issues

1. Workflow not triggering:

  • Check workflow file syntax
  • Ensure proper indentation (YAML)
  • Verify trigger conditions

2. Permission denied:

- uses: actions/checkout@v4
  with:
    token: ${{ secrets.GITHUB_TOKEN }}

3. Dependencies not installed:

- name: Install dependencies
  run: |
    pip install uv
    uv sync --dev

4. Tool not found:

- name: Install system dependencies
  run: |
    sudo apt-get update
    sudo apt-get install -y hadolint

Debug Workflow

- name: Debug Lintro
  run: |
    echo "=== Environment ==="
    python --version
    uv --version

    echo "=== Available tools ==="
    uv run lintro list-tools

    echo "=== File structure ==="
    find . -name "*.py" | head -10

    echo "=== Running Lintro ==="
    uv run lintro check --output-format grid || true

Integration Benefits

Using Lintro in GitHub Actions provides:

  1. Early Issue Detection - Catch problems before they reach production
  2. Consistent Quality - Enforce coding standards across all contributors
  3. Automated Fixes - Reduce manual work with auto-fixing
  4. Comprehensive Reporting - Multi-tool analysis in one place
  5. Quality Gates - Block problematic code from merging
  6. Coverage Tracking - Monitor test coverage over time

Best Practices

  1. Run Lintro early in your CI pipeline (before tests)
  2. Use auto-fix first, then check for remaining issues
  3. Separate workflows for different file types when needed
  4. Cache dependencies to speed up workflows
  5. Use artifacts to preserve reports
  6. Set up quality gates to maintain code standards
  7. Monitor coverage trends over time

This integration transforms your repository into a high-quality, maintainable codebase with automated quality assurance! 🚀