Skip to content

Latest commit

 

History

History
221 lines (128 loc) · 8.61 KB

File metadata and controls

221 lines (128 loc) · 8.61 KB

Rules

Every finding ciledger emits, what triggers it, and how to fix it.

Configured by id in ciledger.config.json:

{ "rules": { "CL004": "off", "CL007": "warning" } }

Settings: "error", "warning", "info", "off".

On severity. error is reserved for findings that cause open-ended, unbounded spend — the ones that produce the bill nobody can explain. Everything else is a warning or an observation, so a default CI gate blocks on runaway cost and nothing else. A tool that fails builds over opinions gets switched off.


CL001 · no-concurrency-cancel · warning

A workflow triggered by pull_request with no cancel-in-progress.

Push three commits to a branch in quick succession and you bill three complete runs. The first two are already irrelevant by the time they finish.

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Quantified: the finding reports the full billable cost of one run, because that is what each superseded run wastes.

Only fires on pull-request workflows. Cancelling in-progress runs on main is usually wrong — you want every merge built.


CL002 · duplicate-trigger · warning

Both push and pull_request fire without a branch filter on push.

Every commit to a branch with an open pull request runs the workflow twice: once for the push, once for the pull-request update. Exactly half of that is waste.

on:
  push:
    branches: [main]      # only the branch you merge into
  pull_request:

Restricting push to a release branch is the correct pattern and is not flagged.


CL003 · no-dependency-cache · warning

A job runs a dependency install with no cache step in sight.

Usually the largest single avoidable cost in a workflow, because it is paid by every matrix job on every run. A nine-job matrix downloading node_modules nine times per run, twenty runs a day, is a lot of minutes spent on bytes that have not changed.

- uses: actions/setup-node@v4
  with:
    node-version: 22
    cache: npm          # one line

Recognises npm, yarn, pnpm, bun, pip, poetry, uv, bundler, go modules, cargo, maven, gradle and composer. Satisfied by actions/cache, Swatinem/rust-cache, or a setup-* action with cache: set — a setup action only caches when explicitly asked to, which is a common oversight.


CL004 · no-path-filter · info

An expensive workflow (default: over 30 billable minutes) with no path filter.

It runs in full for a typo fix in a README.

on:
  pull_request:
    paths-ignore: ['**.md', 'docs/**', 'LICENSE']

info rather than warning because path filters interact with required status checks: a workflow skipped by a path filter never reports, and a branch protection rule waiting for it will block the merge forever. Use paths-ignore on the whole workflow only when it is not a required check; otherwise filter inside the job with if:.


CL005 · no-timeout · error

A job with no timeout-minutes.

GitHub's default is six hours. A job that hangs — a hung test, a prompt waiting on stdin, a network call with no timeout — bills all of it. This is the most common cause of a bill nobody can explain, and it is the one finding here that is genuinely unbounded, which is why it is an error.

jobs:
  test:
    timeout-minutes: 15

Set it a little above the job's normal duration. On a matrix the exposure multiplies: nine jobs at six hours is 54 hours, and on macOS that bills as 540.


CL006 · gratuitous-macos · warning

A macOS job with no sign of platform-specific work.

macOS bills at ten times the Linux rate. A job that could run on ubuntu-latest and does not is a tenfold overcharge on every run.

The check looks for Xcode, xcodebuild, Swift, iOS/watchOS/tvOS, code signing, notarisation, CocoaPods, Carthage and Fastlane, in the job id, name, step names, uses and run bodies. Deliberately generous: a false negative costs nothing, while telling someone their iOS build does not need macOS would discredit the whole tool.

Quantified: the finding reports the difference between what the job costs on macOS and what it would cost on Linux.


CL007 · large-matrix · info

A job expanding to 12 or more instances (configurable).

Not wrong, just easy to lose track of — expansion is multiplicative, and one more value on one axis adds a whole row.

A common structure:

strategy:
  matrix:
    os: [ubuntu-latest]
    node: [20, 22]
    include:
      # full matrix only on the default branch
      - os: ${{ github.ref == 'refs/heads/main' && 'macos-latest' || '' }}

or simply a separate scheduled workflow that runs the exhaustive matrix nightly, while pull requests run a representative subset.


CL008 · fail-fast-disabled · info

fail-fast: false on a matrix of four or more.

When one job fails, every sibling runs to completion and bills in full. That is the right trade-off when you need the whole failure picture at once, and pure waste otherwise. info because it is a judgement call, not a defect.


CL009 · unknown-runner · info

A runs-on label not in the rate catalogue.

The job is priced as a standard Linux runner, which may be wrong in either direction. Reported so the assumption is visible rather than folded silently into the total.

If it is self-hosted, add self-hosted to its labels and it will be costed as free. If it is a GitHub-hosted label the catalogue does not know, please open an issue — the catalogue is data and easy to update.


CL010 · larger-runner · info

A larger runner (ubuntu-latest-8-cores and friends).

These bill from the first minute on every plan and never draw on included minutes — including on public repositories, where standard runners are free. Switching to one for speed turns a free workflow into a paid one, and that transition is invisible in a diff.

Worth confirming the speed is real. A job that is not CPU-bound runs no faster on more cores, and a four-minute job on 8 cores costs the same as a sixteen-minute job on 2.


CL011 · frequent-schedule · warning

A schedule trigger firing more often than hourly (configurable).

Scheduled runs bill whether or not anything changed, and nobody sees them in a pull request. */15 * * * * is 96 runs a day, 2,880 a month.

The finding reports the daily cost — in minutes, or in dollars where the workflow uses runners that draw no included minutes.


CL012 · serial-job-chain · info

A needs chain four or more jobs deep.

Serialising does not reduce billed minutes; it only delays feedback. Jobs that do not consume a previous job's output can drop the needs and run in parallel for the same money.

Sometimes the chain is deliberate — a gate that avoids running expensive jobs after a cheap one fails genuinely saves money. info because the tool cannot tell which.


CL013 · redundant-checkout-depth · info

actions/checkout with fetch-depth: 0 and no step that appears to need history.

Full history is cloned on every matrix job. On a large repository that is a substantial download repeated many times per run.

Suppressed when a step looks like it reads history: git log/describe/rev-list/blame, changelog generation, semantic-release, Nx or Turbo affected-graph commands, or coverage tools that diff against the base.


CL014 · dynamic-matrix · info

A matrix built from an expression, usually fromJSON of a previous job's output.

It cannot be expanded statically, so the job count for that job — and every total containing it — is a lower bound. Reported so the number is never mistaken for exact.

If the JSON comes from a fixed file rather than genuinely dynamic computation, inlining it makes the cost visible.


CL015 · workflow-parse-error · error

A workflow that does not parse.

The jobs it contains are missing from every total, so the reported cost understates the real bill. An error because a silently incomplete cost report is worse than no report.

The parser covers the YAML subset workflows use and refuses anchors, aliases, merge keys and tags rather than mis-parsing them. If a valid workflow is rejected, that is a bug worth reporting.


Suppressing findings

Per repository:

{ "rules": { "CL004": "off" } }

There are no inline suppression comments. Several findings are workflow-level rather than line-level, and a comment on one line could not express which. If a rule is systematically wrong for your setup, switch it off — and please open an issue, since the thresholds are only as good as the workflows they have seen.