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:
- Enable GitHub Pages in repository settings (for coverage badges)
- Push to main branch to trigger workflows
- 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, viapull_request.base.sha), never the PR head. The code that runs with the credential is always trusted, so a PR cannot substitute its ownlintro/**to exfiltrate it. The PR is still reviewed:lintro review --prfetches the diff throughgh(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
clitransport against the pinnedclaudeCLI, authenticated by theCLAUDE_CODE_OAUTH_TOKENsecret (a Claude subscription session).ANTHROPIC_API_KEYis deliberately not in scope, andLINTRO_CLI_BARE: neverkeeps--bareoff the command line so the OAuth session is actually used (#1838). The CLI version is pinned indocker/ai-tools.Dockerfileand installed from npm at that exact version. - 💸 Bounded spend (advisory under the CLI transport) —
ai.max_cost_usdcomes from the trusted base config, so a PR cannot raise the cap. It prices only the tokens lintro billed itself, so on theclitransport — 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 overshootai.max_cost_usdby 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-errorjob rewrites its conclusion tosuccess, 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):
- Same-repo only — the job
ifguard requirespull_request.head.repo.full_name == github.repository(fork PRs never run). - 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. TheclaudeCLI is installed at a version pinned in that same trusted checkout. - Secret ordering —
CLAUDE_CODE_OAUTH_TOKENis injected only into the final review step’senv, 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"
}
}
| Field | Type | Meaning |
|---|---|---|
kind | string (enum) | Canonical classification (see below). Stable across providers. |
provider | string | Provider identifier, lowercased (e.g. anthropic, openai, cursor). |
status | integer | null | Extracted HTTP status (e.g. 401, 429, 529), or null when none. |
retryable | boolean | true for transient conditions safe to retry unchanged. |
message | string | The 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:
| Code | Meaning |
|---|---|
0 | Review completed, no P1 findings. Success envelope on stdout. |
1 | Review completed with P1 findings. Success envelope on stdout. |
2 | Provider/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 levelbranch_protection.yaml,binary_artifacts.yaml,outside.yaml,security.yamleach withoptConfig: { optIn: true }andaction: issueas 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
.allstarrepository with opt-out strategy. - Repo-level configs require org
disableRepoOverrideto 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:
- Go to repository Settings → Pages
- Select Source: “GitHub Actions”
- 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
- On push to
-
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.ymlon tag push
-
Publish to PyPI on Tag (
.github/workflows/publish-pypi-on-tag.yml)- On tag push (e.g.,
1.2.3), verifies tag equalspyproject.tomlversion - Uses Trusted Publishing (OIDC) to upload to PyPI
- Also creates a GitHub Release and attaches built artifacts
- On tag push (e.g.,
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) andcontents: writeonly if creating a GH Release. - PR comment jobs:
pull-requests: write.
- Tag creation job:
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
uvviapip(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-bumpto make them easy to target in policies. - Tagging is handled by
release-auto-tag.yml, which only tags commits matchingchore(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-publishremains onrelease/v1by 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:

Quality Badge

Custom Lintro Badge

OpenSSF Scorecard Badge
Add to your README.md:
[](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:
- Early Issue Detection - Catch problems before they reach production
- Consistent Quality - Enforce coding standards across all contributors
- Automated Fixes - Reduce manual work with auto-fixing
- Comprehensive Reporting - Multi-tool analysis in one place
- Quality Gates - Block problematic code from merging
- Coverage Tracking - Monitor test coverage over time
Best Practices
- Run Lintro early in your CI pipeline (before tests)
- Use auto-fix first, then check for remaining issues
- Separate workflows for different file types when needed
- Cache dependencies to speed up workflows
- Use artifacts to preserve reports
- Set up quality gates to maintain code standards
- Monitor coverage trends over time
This integration transforms your repository into a high-quality, maintainable codebase with automated quality assurance! 🚀