Skip to content

Repository files navigation

CI skill

CI that stays green for the right reasons ( ̄ー ̄)ゞ

Agent Skill GitHub Actions Bash Nix license ci

A green pipeline is worth exactly as much as the question it answers. A check that cannot fail answers nothing, a job that goes red without a code change answers about someone else's mirror, and a badge people have learned to re-run answers about their patience. This skill is the set of rules that keep those three from happening, plus the templates and the terminal harness that make following them the cheap path

It teaches an agent to write and review CI: what may gate a pull request and what has to stay a weekly drift detector, why every binary a job runs comes from the repo's own lockfile, how dependency bumps land themselves without a human babysitting them, how a readme earns one honest badge per statement, and why a checker that has never been red is a decoration

Contents

Install

git clone https://github.com/rokokol/ci-skill ~/Projects/ci
ln -s ~/Projects/ci ~/.claude/skills/ci

Or straight into the skills directory your agent reads:

git clone https://github.com/rokokol/ci-skill ~/.claude/skills/ci

Note

A skill has no version to pin — it is read at whatever revision you have checked out, so git pull is the whole upgrade path and the changelog is dated rather than numbered

Then ask Claude Code to write, review or check CI, push to a repository that has it, or reach for it by name. SKILL.md carries the rules, references/ the reasoning behind each, templates/ the copyable workflow files with EXAMPLE markers where a repo has to fill in its own

The rules

Gate or detector, never both Checks that depend only on the repo gate pull requests. Checks that depend on someone else's uptime — mirrors, :latest images, live sites — run on push, on a weekly cron and by hand, never on PRs: a Debian mirror having an afternoon must not redden someone's rename, and the weekly run is the drift alarm those checks exist to be
Everything pinned Actions by version under dependabot, tools from the repo's own lockfile — nix develop, npm ci, cargo --locked — never nix run nixpkgs#tool, npx tool@latest, pip install tool. An unpinned lookup is a mirror-fate test: the job changes behaviour with zero change in the repo. check-pins.sh, one file vendored into every repository that runs it, greps the workflows and fails on it, proving per shape on every run that it can
The build workflow is callable workflow_call with a ref input, so bots verify a branch by running the real workflow instead of a copy of its commands that drifts away from it
Bumps land themselves, on green A weekly bump→verify→land cascade: bump onto a dated branch, verify by calling the build workflow against it, fast-forward and delete only on green — red leaves the branch standing for a human
A file that travels is vendored A checker another repository needs is a verbatim copy there, listed in a lock with the commit it came from and the blob it must still be. The gate refuses a copy edited in place, and a weekly cascade of the same shape takes each source's newer content, so a fix made once reaches every copy
One badge per statement A status badge is per workflow file, so anything deserving its own badge gets a thin wrapper delegating to one reusable job. The wrappers differ by name and input; the logic lives once
Every check is proven able to fail A new check runs red first against the pre-fix state or a deliberately broken fixture. Checkers exercise known-bad inputs, and assertions on generated text match whole lines, not substrings
One source of truth per list Lint file lists, tool sets and version numbers each live in exactly one place the others read. Duplicated lists drift, and drifted lists lie
Least privilege, bounded time permissions: contents: read at every workflow's top, widened per job only where a job writes; timeout-minutes on anything that talks to the network or can wait for input; concurrency on anything that pushes
A push is not done until its runs conclude Run the gate locally with the workflow's own command first, push, then ci.sh watch to a verdict — and report the verdict, not the push. Red means ci.sh failed and a fix; a rerun is for an external cause that has been named

The harness

ci.sh is the operational half — the questions you would otherwise re-derive from gh run flags every time:

Command What it answers
ci.sh status the badge row in a terminal: every workflow's latest run, one line each
ci.sh runs [N] the recent runs with their ids, for picking a target
ci.sh watch blocks until every run of the current HEAD concludes, nonzero if any failed — the after-push command
ci.sh failed [ID] which steps failed, then the log around the real error
ci.sh log [ID] [JOB] the whole log of one job, whatever it concluded — the first green run of a new job is the one worth reading rather than trusting
ci.sh dispatch WF [REF] fire a workflow_dispatch and follow it to a verdict
ci.sh rerun [ID] rerun a run's failed jobs and follow

Every subcommand takes -R owner/repo to aim at another repository; without it, gh's own default applies. ci.sh failed exists because gh run view --log-failed ends each job with the runner's teardown — credential unsets, orphan reaping — dozens of lines that bury the error, and on a multi-job run the tail you land on is often the wrong job's

Templates

templates/github/workflows/ holds the workflow files the rules describe, and beside them the checkers worth having in any repository:

github/workflows/
  build.yml            the gate: workflow_call + ref, calls the pin guard, least privilege
  bump-cascade.yml     weekly bump -> verify by calling build.yml -> land on green
  detector.yml         the reusable world-facing job
  detector-target.yml  the thin wrapper that gives that job its own badge
  vendor-sync.yml      weekly refresh of vendored files -> verify by calling build.yml -> land on green
no-secrets.sh          refuse ignored paths and secret values at the tracked-file boundary
check-pins.sh          the pin guard: no tool from a registry, proven per shape on every run
check-interface.sh     hold documents to the interface a foreign tool declares, falsifying itself on every run
vendor-sync.sh         keep vendored copies byte-equal to their source, and refuse an edit in place

no-secrets.sh rejects tracked paths matched by .gitignore, including paths admitted with git add -f, before scanning tracked contents for secret shapes; .gitignore itself remains ordinary repository content. EXAMPLE markers sit on everything repo-specific — the bump command, what the detector probes, the secret shapes only your repo can leak. The other checkers have no such part, and another repository takes them through the vendoring cascade rather than by hand. What check-pins.sh catches and how it proves itself is in pinning.md, and the same for check-interface.sh in checks.md

Important

Copying the secret gate proves nothing. The mechanism travels, the knowledge does not — a copy is worth running only once it has been falsified in its own repository: break what it watches, see red, put it back

Tests

nix develop -c ./check-templates.sh

Lints the scripts and every checker template, runs actionlint over every workflow template, runs the skill-authoring skill's check-skill.sh, vendored, on this repository's own docs and check-pins.sh on its workflows and the workflow templates, holds ci.sh and the travelling checkers to their own help with the bash-best-practices skill's check-sh.sh — every subcommand, flag and exit code in the help, every ci.sh mention in the docs a real one — then proves each check can fail. actionlint must reject the known-bad workflow in tests/fixtures/. The pin guard plants every shape it claims to catch and every pinned spelling it must stay quiet on, itself. The secret gate is exercised end to end in a throwaway repository: clean while scanning only its own source, red on user/preferences.md covered by .gitignore and admitted with git add -f, then red on each planted key shape in turn, naming every finding. And the skill gate plants a defect per check in copies of this repository and requires itself to go red on each. check-interface.sh runs on two fixtures, one per shape of interface it reads — a CLI whose flags are bare words, an MCP server whose tools take named arguments — each showing one wrong call on purpose that its .allow file excuses, planting its own defects and excuses on each, and must refuse with 2 a call it cannot serve. vendor-sync.sh is driven end to end against a source repository reached over file://: a copy taken with its executable bit, a source commit brought over and recorded in the lock, an unrelated one kept out of it, a vendored directory following deletions and additions, an edit in place named by check and refused by update, a workflow file taken only as a manual line, and the script updating itself to a longer version while it runs. Every one of those halves was watched failing before it was trusted

Layout

SKILL.md             the rules an agent reads
ci.sh                the harness: status / runs / watch / failed / log / dispatch / rerun
references/          one spec per rule: workflows, pinning, badges, bump-cascade, checks, ops
templates/           copyable workflows, no-secrets.sh, and the travelling check-pins.sh, check-interface.sh and vendor-sync.sh
check-templates.sh   the self-testing template lint
check-skill.sh       the gate every skill repository shares, vendored from the skill-authoring skill
check-sh.sh          holds ci.sh and the travelling checkers to their own help, vendored from the bash-best-practices skill
vendor-sync.sh       this repository's own copy of its template, kept current by the cascade like any other
tests/fixtures/      the known-bad inputs the checks must fail on