GitLab CI
One job in the .post stage, gated on when: on_failure. It reads the pipeline it belongs to and writes its report as artifacts.
The minimal job
Deterministic report, artifacts on every failure, no model required:
# runs only on failure; always exits 0.
ci-doctor:
stage: .post
image: registry.internal.example.com/ci-doctor:latest
rules:
- when: on_failure
allow_failure: true
variables:
CI_DOCTOR_GITLAB_TOKEN: "$CI_DOCTOR_TOKEN" # a read-scoped API token
script:
- ci-doctor analyze "$CI_PIPELINE_ID"
artifacts:
when: always
expire_in: 1 week
paths:
- report.md
- report.jsonCI_PIPELINE_ID and CI_PROJECT_ID are predefined by GitLab, so the only thing you supply is the token. It needs read_api — and apionly if you turn on merge-request notes below.
Add an LLM explanation
Point at any OpenAI-compatible endpoint (swap in place of the deterministic-only variables):
variables:
CI_DOCTOR_GITLAB_TOKEN: "$CI_DOCTOR_TOKEN"
CI_DOCTOR_LLM__MODEL: "qwen2.5-coder:32b"
CI_DOCTOR_LLM__API_BASE: "http://vllm.internal:8000/v1"
# CI_DOCTOR_LLM__API_KEY... uses api_key_env if your gateway needs oneOptional at every step: with no model the job still emits the full deterministic report. The other backends are on Configuration .
Post a note on the merge request
Idempotent — it updates its own previous comment instead of spamming. Gated on confidence ≥ medium.
variables:
CI_DOCTOR_GITLAB_TOKEN: "$CI_DOCTOR_TOKEN"
CI_DOCTOR_OUTPUT__MR_NOTE: "true" # post/update one note when confidence >= mediumNeeds CI_MERGE_REQUEST_IID, which GitLab sets on merge-request pipelines, and a token with api scope rather thanread_api.
Self-hosted GitLab
Override the API host and hand it your internal CA. Proxy variables are honoured as-is:
variables:
CI_DOCTOR_GITLAB_TOKEN: "$CI_DOCTOR_TOKEN"
CI_DOCTOR_GITLAB__BASE_URL: "https://gitlab.internal.example.com"
CI_DOCTOR_GITLAB__CA_BUNDLE: "/etc/ssl/certs/internal-ca.pem"HTTPS_PROXY, NO_PROXY and REQUESTS_CA_BUNDLE behave the way you would expect. For runners with no egress at all, seeLocal & air-gapped .
allow_failure: true is belt-and-braces.