{"slug": "ci-health-check-a-ci-skill-for-claude-code", "title": "CI Health Check: a /ci Skill for Claude Code", "summary": "Hudson Atwell, a GBTI Network Member, created a /ci skill for Claude Code that audits GitHub Actions workflows. The skill provides a health board, failure triage, post-push monitoring, and a workflow inventory, encoding common CI debugging motions that agents and humans repeatedly perform. It uses the gh CLI and includes non-obvious behaviors for fetching logs and attributing scheduled failures.", "body_md": "**By Hudson Atwell, GBTI Network Member.** Originally published on\n\nClaude Code loads any markdown file at `.claude/skills/<name>/SKILL.md`\n\nas a reusable slash command (a \"skill\"). This one gives your agent a `/ci`\n\ncommand that audits your GitHub Actions: a red/green health board, real failure triage, a post-push watcher, and a living inventory of what every workflow does.\n\nIt exists because agents (and humans) keep re-deriving the same motions every time CI goes red: which runs failed, how to actually get the logs, whether the failure is your commit or something that was already broken. The skill encodes those motions once, including a few non-obvious `gh`\n\nbehaviors that cost me real debugging time.\n\n`.claude/skills/ci/`\n\nin your repo.`.claude/skills/ci/SKILL.md`\n\n.`/ci`\n\n(or `/ci health check`\n\n) in Claude Code.Requires the `gh`\n\nCLI authenticated against your repo. The skill runs from the project folder, so the repo is already known: `gh`\n\nresolves it from the working directory, and the `{owner}/{repo}`\n\ntokens in API paths are native `gh api`\n\nplaceholders it fills in for you. Nothing to configure.\n\n```\n---\nname: ci\ndescription: \">\"\n  Inspect and diagnose this repo's GitHub Actions CI. Invoke for \"/ci\", \"/ci health\", \"/ci health check\",\n  \"/ci watch\", \"/ci schedule\", \"/ci diagnose <run-id>\", \"/ci list\", or when the user asks whether CI is\n  green, why a workflow failed, or what a workflow does. Pulls recent runs with gh, downloads failed job\n  logs the reliable way, and triages failures to their real root cause.\n---\n\n# CI operations\n\nAll commands run from the repo root with the `gh` CLI (it resolves the repo from the working directory;\n`{owner}/{repo}` in API paths is a native gh placeholder). Default action when none is named: `health`.\n\n## Tooling lore (read first)\n\n- **Fetching logs:** `gh run view <id> --log-failed` often returns NOTHING. The reliable recipe:\n  ``` bash\n  JOB=$(gh run view <run-id> --json jobs -q '.jobs[0].databaseId')\n  gh api repos/{owner}/{repo}/actions/jobs/$JOB/logs > /tmp/job.log\n  ```\n  Then grep the file; strip the timestamp column with `cut -c30-` when quoting. Multi-job runs: iterate\n  `.jobs[]` and pick by `.conclusion == \"failure\"`. Step-level status without logs:\n  `gh api repos/{owner}/{repo}/actions/runs/<id>/jobs -q '.jobs[0].steps[] | .name + \" \" + .conclusion'`.\n- **Scheduled-failure attribution:** the failure email for a SCHEDULED workflow cites the LATEST main sha,\n  which is often NOT the commit that broke it. Always check\n  `gh run list --workflow <file> --limit 5 --json conclusion,createdAt,event` first; if the failures predate\n  the cited commit, it is a standing provisioning or external problem, not a regression.\n- **Secrets vs variables:** repo secrets via `gh secret list`, plain variables via `gh variable list`. A\n  workflow reading `secrets.X` where X was never created gets an EMPTY string, not an error, so the symptom\n  is a downstream \"not set\" message, a 401, or an empty env var in the step header.\n- **Setting secrets may be gated:** an agent session may be blocked from `gh secret set` by permission\n  policy. Prepare the value in a local untracked file and hand the human the one command.\n\n## Failure triage (a starting taxonomy — extend it to fit the project)\n\nClassify every red into a named bucket, because the bucket decides the response. These three cover most\nrepos; add project-specific buckets as you meet them (examples: environment/toolchain drift, a dependency\nor upstream API regression, resource exhaustion such as OOM or disk or rate limits, data- or state-dependent\nfailures, expired credentials). When a failure fits no bucket, name a new one in the report rather than\nforcing it into a wrong response.\n\n1. **Broken by commit:** the failure starts at a specific sha and the log implicates changed files. Fix the\n   root cause (see Fix discipline below); verify with a rerun on the fix commit.\n2. **Provisioning gap:** missing or empty secret, unset variable, an external account not configured. Route\n   to the human with the exact command; do not retry.\n3. **Flaky / external:** network hiccup, provider outage, rate limit; the same job passed before and after\n   without a related change. `gh run rerun <id> --failed` once, then re-check.\n\n## Fix discipline (failing tests especially)\n\nWhen a test fails, evaluate the REAL defect the test is exposing and propose a fix for that root cause.\nNever patch the symptom, and never modify a test so it passes while the underlying failure remains — if you\nfind yourself weakening an assertion, deleting a case, or special-casing the test input, stop and re-derive\nwhat the test was protecting. Changing a test is only correct when the test itself is wrong about the\nintended behavior, and the report must say that explicitly and justify it. The same rule generalizes beyond\ntests: a fix that makes the red go away without explaining WHY it was red is a symptom patch, not a fix.\n\n## Actions\n\n### /ci health [N]   (also: /ci health check; the default)\n\n1. `gh run list --limit ${N:-30} --json databaseId,workflowName,conclusion,headSha,event,createdAt` and\n   group by workflow. Report a red/green board: latest conclusion per workflow, streak (consecutive fails),\n   and the event (push vs schedule).\n2. For each currently-red workflow: pull its recent history (`--workflow <file> --limit 5`) to date the\n   breakage, download the failed job log (recipe above), and triage it (the taxonomy above) with a\n   one-line root cause and the proposed fix.\n3. End with the board, the diagnoses, and what to do next. Offer to make low-risk code-side fixes (root\n   cause, per Fix discipline); provisioning gaps go to the human.\n\n### /ci watch\n\nThe post-push ritual. Find the runs for the current HEAD and watch until all conclude:\n``` bash\nSHA=$(git rev-parse HEAD)\ngh run list --limit 15 --json databaseId,workflowName,conclusion,headSha \\\n  -q \".[] | select(.headSha==\\\"$SHA\\\")\"\ngh run watch <id> --exit-status   # per unfinished run\n```\nReport each result; diagnose any red as in health.\n\n### /ci drift   (only if your repo commits build artifacts)\n\nThe LOCAL pre-push check that committed artifacts match their source:\n``` bash\n<your full artifact build command(s)>\ngit diff --name-only -- <artifact-dir-1> <artifact-dir-2>\n```\nEmpty diff = safe to push. Non-empty = stage those files with the commit that changed the source. Fill in\nEVERY build command: partial rebuilds that skip one artifact are the classic way this check reds your main\nbranch anyway.\n\n### /ci schedule\n\nStaleness audit of the scheduled workflows. For each one (list yours here with cadences): pull the last 5\nruns and report the last SUCCESS date. Alarm on any workflow whose last success is older than 2x its\ncadence. A scheduled job can be silently red for days; nobody rereads yesterday's failure email. Note which\nscheduled jobs are load-bearing (a backup, a data sync something else depends on) so staleness there is\nescalated, not just listed.\n\n### /ci diagnose <run-id | workflow-name>\n\nDeep-dive one run (or the latest run of a named workflow): step table, failed job log to disk,\ntriage bucket, root cause, fix proposal.\n\n### /ci rerun <run-id>\n\n`gh run rerun <run-id> --failed` then watch it. Only for the flaky/external bucket; never rerun a\nprovisioning gap (it cannot pass) or a broken-by-commit red (fix the root cause first).\n\n### /ci list\n\nPrint this workflow inventory (keep it current when workflows are added or changed):\n\n| Workflow (file) | Trigger | What it does | Needs |\n|---|---|---|---|\n| <Name> (<file>.yml) | push / PR / cron | <one line on what it validates or does> | <secrets or nothing> |\n\nTo seed it, read every file in `.github/workflows/` and summarize: name, trigger, the job's purpose (the\nheader comment usually says), and which secrets it reads.\n\n## Reporting conventions\n\n- Lead with the board (workflow, latest state, streak), then diagnoses, then actions taken or proposed.\n```\n\nThere is nothing to configure for the repo itself: `gh`\n\ninfers it from the working directory, so the skill works the moment you drop it in. Only two parts are inherently repo-specific:\n\n`.github/workflows/`\n\nand then treat it as living documentation. This is the part future sessions (and new contributors) thank you for.And treat the failure taxonomy as a starting point, not a fixed set: projects fail in project-shaped ways, so add the buckets yours actually produces.\n\nEach lore item is a real failure mode: `--log-failed`\n\nsilently returning nothing while the jobs API works; a scheduled backup that failed for four days while its failure emails blamed whatever commit happened to be newest on main; a workflow reading a secret nobody ever created and reporting it as a vague downstream error instead of failing fast; a drift check that stayed red because the rebuild command regenerated only one of two committed bundles. The Fix discipline section is there because agents notoriously \"fix\" a failing test by editing the test — the classification buckets plus that rule keep the agent from the three classic wastes: rerunning a job that can never pass, \"fixing\" code that was never broken, and silencing a test that was telling the truth.", "url": "https://wpnews.pro/news/ci-health-check-a-ci-skill-for-claude-code", "canonical_source": "https://dev.to/gbti-network/ci-health-check-a-ci-skill-for-claude-code-2njg", "published_at": "2026-07-11 23:44:04+00:00", "updated_at": "2026-07-12 00:13:37.266344+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "large-language-models", "mlops"], "entities": ["Hudson Atwell", "GBTI Network", "Claude Code", "GitHub Actions", "gh CLI"], "alternates": {"html": "https://wpnews.pro/news/ci-health-check-a-ci-skill-for-claude-code", "markdown": "https://wpnews.pro/news/ci-health-check-a-ci-skill-for-claude-code.md", "text": "https://wpnews.pro/news/ci-health-check-a-ci-skill-for-claude-code.txt", "jsonld": "https://wpnews.pro/news/ci-health-check-a-ci-skill-for-claude-code.jsonld"}}