Skip to content
A workflow step running pnpm test is flagged P001 because the Taskfile already runs it; ci-parity fix rewrites the step to task test.A workflow step running pnpm test is flagged P001 because the Taskfile already runs it; ci-parity fix rewrites the step to task test.

ci-parity

Check that GitHub Actions workflows run project commands through the task runner, instead of calling tools inline. A per-workflow baseline lets you adopt it today and only ever shrinks.
A terminal session: ci-parity check reports three inline tools, each already available as a task; ci-parity fix rewrites the three steps; ci-parity check then reports 0 errors.

The same check on a workflow that calls pnpm directly, next to a Taskfile that already wraps those commands:

ci-parity check
.github/workflows/ci.yml
9:14 P001 inline-tool step "Install" in job "test" runs 'pnpm install --frozen-lockfile' directly; already available as 'task install'
11:14 P001 inline-tool step "Test" in job "test" runs 'pnpm test' directly; already available as 'task test'
13:19 P002 unknown-task 'task lint' is not defined in Taskfile.yml (closest: test)
3 errors, 0 warnings, 0 tolerated, 0 allowed

Each finding names the step, the command, and the task that already runs it. Getting started walks through it.

flowchart LR
    wf["Workflows and<br/>composite actions"] --> cp
    rf["Taskfile, Makefile,<br/>justfile or mise.toml"] --> cp
    bl["Baseline"] --> cp
    cp(["ci-parity<br/>reads them, runs nothing"]) --> ann["Annotations on<br/>the pull request"]
    cp --> cs["Code scanning<br/>alerts"]
    cp --> fix["ci-parity fix<br/>rewrites safe steps"]

It reads the files as text: it never runs your task runner, your scripts or the network. Every finding is on the exact line and column, in four formats.

With the GitHub Action, each finding lands on its line in the pull request, and with SARIF it shows up in code scanning too. These are from a real pull request on this repository.

A pull request's Files changed tab: under line 21, run: golangci-lint run ./..., an annotation reads P001 inline-tool, already available as 'task lint:go', with the matching code scanning alert below it.
An annotation and a code scanning alert on the line that caused them
The ci-parity code scanning check on a pull request: 4 new alerts including 4 errors, each listed with its line and message.
The code scanning check: every new alert, with its rule

CI setup has both workflows.

.github/workflows/ci.yml
- uses: go-task/setup-task@v2
- run: task test

The default: no config needed. Internal tasks (internal: true) are reported as P003.

Recipes has a passing setup for each.

A repository with dozens of inline steps doesn’t have to fix them first. ci-parity baseline records each workflow’s count, the check tolerates that many, and the count can only go down.

The baseline ratchetAn example workflow starts with 12 inline steps in its baseline, then 9, 6, 2, and finally 0, where it is locked: each run of ci-parity baseline records a lower count, and a count can never go back up.12Adopt9Week 26Week 42Week 60 🔒Locked
Illustration: inline steps allowed by one workflow's baseline. Each ci-parity baselinelocks in a lower count, and a rise fails the check.

Adopting with a baseline shows each step, and ci-parity fix rewrites the steps it safely can.

P001 inline-tool

A run: step calls pnpm, go, docker or another tool directly. Fix or allow it →

P002 unknown-task

CI calls a task the runner doesn’t define, with the closest name. Details →

P003 internal-task

CI calls an internal, private or hidden task. Details →

P004 regression

A workflow has more inline steps than its baseline allows. Details →

P005 unsupported

A step it can’t analyze, such as a PowerShell step, is reported as a warning. Details →