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:

.github/workflows/ci-doctor.yml
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@master

Which ref to pin

PinYou get
@masterThe latest code. What the examples here use.
@v1.2.3Exactly 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

InputDefaultWhat it does
run-idthe triggering runWorkflow run to analyze.
tokengithub.tokenReads the run, its jobs and their logs.
repositorycurrent repoRepository to read, as owner/name.
job-idAnalyze one job instead of every failed job.
configrepo rootPath to a .ci-doctor.yml.
post-pr-notefalsePost/update one idempotent PR comment. Needs pull-requests: write.
api-urlgithub.api_urlOverride for GitHub Enterprise.
working-directory.Where the reports are written.

Outputs

OutputValues
phaseprovision, prepare, fetch, script, post
categorytest, build, dependency, infrastructure, runtime, config, …
confidencehigh, medium, low
is-infra-not-code"true" when the failure looks like infrastructure rather than the change
markdown-pathpath to the rendered Markdown report
json-pathpath 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: true

Add 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/v1

Act 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.

It always exits 0. The analyze step runs with 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.