GitHub Actions
One step, no install. It reads the run that triggered it, works out what broke, and writes a report — without ever failing your workflow. This page is both the setup guide and the full action reference.
Minimal setup
Wire it to workflow_run on the workflow you want explained. Every input has a default that does the right thing on the triggering run:
name: ci-doctor
on:
workflow_run:
workflows: ["CI"] # the workflow whose failures you want explained
types: [completed]
jobs:
explain:
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
runs-on: ubuntu-latest
permissions:
actions: read # read the failed run, its jobs and their logs
contents: read
steps:
- uses: fennet82/ci-doctor@masterWhich ref to pin
| Pin | You get |
|---|---|
@master | The latest code. What the examples here use. |
@v1.2.3 | Exactly that release. Never moves. |
@<sha> | Immutable, and what a strict supply-chain policy wants. |
There is no floating @v1: releases are cut by semantic-release, which publishes the exact tag and nothing else. Pin a release tag or a SHA when you need reproducibility.
Inputs
| Input | Default | What it does |
|---|---|---|
run-id | the triggering run | Workflow run to analyze. |
token | github.token | Reads the run, its jobs and their logs. |
repository | current repo | Repository to read, as owner/name. |
job-id | — | Analyze one job instead of every failed job. |
config | repo root | Path to a .ci-doctor.yml. |
post-pr-note | false | Post/update one idempotent PR comment. Needs pull-requests: write. |
api-url | github.api_url | Override for GitHub Enterprise. |
working-directory | . | Where the reports are written. |
Outputs
| Output | Values |
|---|---|
phase | provision, prepare, fetch, script, post |
category | test, build, dependency, infrastructure, runtime, config, … |
confidence | high, medium, low |
is-infra-not-code | "true" when the failure looks like infrastructure rather than the change |
markdown-path | path to the rendered Markdown report |
json-path | path to the structured JSON report |
Comment on the pull request
Idempotent — it updates its own previous comment instead of piling up. Skipped below medium confidence.
permissions:
actions: read
contents: read
pull-requests: write # only needed for the PR comment
steps:
- uses: fennet82/ci-doctor@master
with:
post-pr-note: trueAdd an LLM explanation
Optional. Without it you still get the full deterministic report. Any OpenAI-compatible endpoint works — see Configuration for the other backends.
- uses: fennet82/ci-doctor@master
env:
CI_DOCTOR_LLM__MODEL: qwen2.5-coder:32b
CI_DOCTOR_LLM__API_BASE: http://vllm.internal:8000/v1Act on the verdict
The outputs are the point: they let the workflow branch on what broke.
- uses: fennet82/ci-doctor@master
id: doctor
# Route infrastructure failures somewhere other than the author's inbox.
- if: ${{ steps.doctor.outputs.is-infra-not-code == 'true' }}
run: gh issue create --label infra --title "Runner trouble in ${{ github.workflow }}"
- uses: actions/upload-artifact@v4
if: always()
with:
name: ci-doctor-report
path: ${{ steps.doctor.outputs.markdown-path }}GitHub Enterprise
- uses: fennet82/ci-doctor@master
with:
api-url: https://ghe.internal/api/v3
token: ${{ secrets.GHE_TOKEN }}How it installs
It is a composite action, not a Docker one: it installs the source tree at the ref you pinned, so the action version and the analyzer version cannot drift. The install goes into a virtualenv under $RUNNER_TEMP — since Ubuntu 24.04 the runner's system Python is PEP 668 externally-managed and a plain pip install fails.
set +e and a guarded read of the report, so a crash, a malformed report, or an unreachable LLM leaves the outputs empty rather than failing your workflow. Adding this step cannot break your CI.