Automated summary
Diagnoses GitLab CI pipeline failures and hardens .gitlab-ci.yml by matching symptoms against a repo-specific taxonomy of recurring failure classes.
Strengths
- Starts from live pipeline facts (pipeline ID, failed jobs, job trace) through the GitLab API instead of guessing from the commit diff.
- Provides a concrete symptom-to-root-cause taxonomy (first-contact tag jobs, silent green, artifact-path drift, missing release-gate wiring).
- Captures repo-specific CI rules such as YAML anchors not crossing include boundaries, so templates must use extends or !reference.
- Includes practical auth and branch quirks for glab and raw API usage.
Limitations
- Hard-coded to the futo-notes/futo-notes GitLab project and its ci/<group>.yml layout.
- Requires network access and a working GITLAB_TOKEN; the provided text also appears cut off mid-entry.
- The taxonomy is historical and may miss genuinely new failure modes.
Use when a GitLab pipeline is failing, a release/tag job did not publish, or before editing .gitlab-ci.yml or tagging a release.
Do not use for CI systems other than GitLab, or for a general YAML syntax review unrelated to this repository's pipeline behavior.
Security analysis
CautionNo destructive, obfuscated, or exfiltrating actions: the only secret use is an env-var token sent to the legitimate GitLab API endpoint for the repo, and nothing is piped to curl|sh or decoded. It does rely on Bash, network calls, and Edit of CI config, and contemplates pushing a throwaway branch, so it belongs in 'caution' rather than 'safe', but it is a legitimate diagnostics workflow with explicit guardrails (no silent green, ask before tagging).
- •Uses Bash/curl with a private GitLab token (`$GITLAB_TOKEN` via `PRIVATE-TOKEN` header) against a remote API; the token is read from the environment rather than hardcoded, which is correct, but the skill does exercise authenticated network access.
- •Step 4 suggests pushing a temporary branch with a `rules:` override to dry-run tag-only jobs, i.e. it expects the agent to commit/push to the real remote; it does caution to revert before merge and to ask the user before any retag.
- •Grants Edit on `.gitlab-ci.yml`/`ci/*.yml` and guides CI/publish pipeline changes — high-impact configuration surface, though changes are reviewable and the skill repeatedly insists on verification and ask-first for publishing.
- •Skill routinely makes authenticated network calls to the GitLab API (gitlab.futo.org) using a personal access token from $GITLAB_TOKEN, and prints job traces — traces could contain secrets if run verbatim on hostile logs, though nothing here exfiltrates data off-site.
- •Guidance to edit .gitlab-ci.yml and, in Step 4, to temporarily push branch-scoped rule overrides that make tag-only jobs run on a throwaway branch; legitimate but modifies release/publish configuration, so changes must be reverted as instructed.
- •Suggests ad-hoc shell loops (watch_pipeline, curl | jq, tail on traces) that are read-only, but the skill assumes the token has broad API scope with no least-privilege note.
Examples
The pipeline on main is red. Get the latest pipeline, list the failed jobs, and show me the tail of the trace for the first failure before proposing a fix.publish:android failed on the v1.2.0 tag build — check the job trace, classify the failure against our known CI failure classes, and patch .gitlab-ci.yml if needed.I'm about to tag a release. Run the pre-tag check against the current .gitlab-ci.yml and flag any release-gate or artifact-path issues.name: ci-doctor description: Diagnose and fix GitLab CI/pipeline failures, and harden .gitlab-ci.yml against this repo's recurring failure classes. Use when the user says "pipeline failed", "CI is red", "the tag build failed", "publish:android/publish:ios failed", "check the pipeline", "why didn't the release go out", "harden CI", or before tagging a release ("pre-tag check"). Also load this BEFORE editing .gitlab-ci.yml for any reason — it encodes the hard rules that past pipeline breakage taught. allowed-tools: Bash, Read, Edit, Grep, Glob
CI Doctor
.gitlab-ci.yml is the most-churned file in this repo — CI firefighting is the largest recurring
time cost. This skill turns the historical failure classes into a triage procedure so a red
pipeline costs minutes, not an afternoon of push-and-watch.
The root .gitlab-ci.yml holds default, variables, workflow, stages, and an include:
list; the jobs live in ci/<group>.yml. A YAML anchor (&x/*x) does not resolve across
included files, so a template used from another file goes through extends: or !reference.
Text checks read the whole pipeline through scripts/lib/gitlab-ci.mjs.
Project: futo-notes/futo-notes on gitlab.futo.org. The curl calls below use $GITLAB_TOKEN,
which always works. glab is usually also present, but whether it can authenticate is per-machine
and per-shell — see the auth gotcha in docs/agents/issue-tracker.md before blaming a read.
Step 1 — Get the facts (never guess from the commit diff alone)
API="https://gitlab.futo.org/api/v4/projects/futo-notes%2Ffuto-notes"
H='--header PRIVATE-TOKEN:'"$GITLAB_TOKEN"
# Latest pipelines for a ref (branch or tag)
curl -s $H "$API/pipelines?ref=main&per_page=5" | jq '.[] | {id, status, ref, sha: .sha[0:8], web_url}'
# Jobs in a pipeline — find the failures
curl -s $H "$API/pipelines/<PIPELINE_ID>/jobs?per_page=100" \
| jq '.[] | select(.status=="failed" or .status=="canceled") | {id, name, stage, status, allow_failure}'
# The job log (the tail is where the truth is)
curl -s $H "$API/jobs/<JOB_ID>/trace" | tail -80
glab ci status / glab ci view work for quick looks; use the raw API when you need job traces
or to script a watch loop.
For pipelines on the current branch, glab ci list -b <branch> may parse the branch as a date and
fail. Use the encoded project path through glab api:
BRANCH=$(git branch --show-current)
glab api "projects/futo-notes%2Ffuto-notes/pipelines?ref=$BRANCH"
Read the log tail FIRST, then the failing job's script: in its ci/*.yml file, then the diff of
the commit that broke it (git log --oneline -5 -- .gitlab-ci.yml ci/).
Step 2 — Classify against the known failure taxonomy
Match the symptom before inventing a novel diagnosis. These classes account for most historical red pipelines here:
| Class | Symptom | Root cause pattern |
|---|---|---|
| First-contact tag job | A publish/release job fails on its first real run at tag time | Job only executes on tags, so it was never exercised: secrets not propagated (especially into nested VMs like Cirrus), cold caches hitting timeouts, wrong artifact lookup paths |
| CWD/path assumption | A step "succeeds" but its effect didn't happen; later step fails mysteriously | GitLab preserves cd across script lines; relative path resolved elsewhere; -f/|| true masked it |
| Silent green | "Job succeeded" but nothing was published/uploaded | An error branch special-cased to exit 0 |
| Missing release-gate wiring | A test failed but the release published anyway | New test job absent from release:gate.needs (deliberate exceptions: test:audit, test:localization-audit, test:quality-report — see rule 3) |
| Artifact-path drift | No files to upload / downstream job can't find inputs | Workspace layout changed; artifacts:paths/lookup globs didn't |
| Cache death spiral | Retries keep timing out at the same wall | Caches upload only on_success, so every retry starts cold |
| Flake | Same job passes on retry with no change | Fixed timeout or exact-string assertion in a cross-platform test |
| Environment skew | Works locally, fails in CI image | Tool version differs (strip/binutils, glibc, NDK, Xcode) — check the job's image:/runner tag |
Step 3 — Fix by the hard rules
Apply the rule for the class; do not just patch the symptom:
- Absolute paths only in CI scripts — anchor on
$CI_PROJECT_DIR. Never rely on acdfrom an earlier line. After any destructive/cleanup line, verify the effect (ls,test -f) rather than trusting-f. - No silent green. Every error path exits non-zero. If a failure is genuinely acceptable,
model it as
allow_failure: trueon the JOB (visible in the UI), never asexit 0in the script. Assert outcomes: artifact exists, upload count > 0, store accepted the bundle. - Every new test job goes into
release:gate.needsin the same commit. Publish jobs start as soon as their ownneedssucceed, so a red test job that is missing from that list does not hold anything back — that is how v1.4.0 twice came within one job of publishing past a failed test (therelease:gate.needscomment inci/publish.ymlnames both). Deliberate exceptions:test:audit,test:localization-audit, andtest:quality-reportareallow_failure: trueand left out on purpose (docs/architecture-gates.md) — adding one would turn a reporter into a release blocker. - Secrets into nested environments are passed explicitly (Cirrus VM, docker-in-docker). Never assume a CI variable propagates one level down.
- Caches upload
when: alwaysso a timed-out job doesn't doom every retry to a cold start. - Flakes get root-caused — wait on conditions/events, not durations; don't assert exact user-facing strings. One commented timeout bump is the ceiling; a second bump on the same job means find the real cause.
- Keep MR-blocking vs
allow_failure/manual intent explicit inrules:— don't accidentally promote a manual job to blocking or vice versa.
Step 4 — Verify without burning a tag
- MR-scoped jobs: push a branch/MR and watch that pipeline.
- Tag-only jobs: never verify by tagging. Options, in order of preference:
- Run the job's script locally with the same inputs (most publish scripts are
scripts/*.py|mjs|sh— they run outside CI). - Push a temp branch with a one-commit rule override making the job run on that branch
(
rules: - if: $CI_COMMIT_BRANCH == "ci-dryrun-x"); revert before merge. - For the updater path specifically:
node scripts/release-build.mjs e2erehearses build→sign→serve→verify end-to-end with localdev keys.
- Run the job's script locally with the same inputs (most publish scripts are
- After a failed tag: fix on main first. Then ask the user before any retag/new tag (publishing is an ask-first action). Repo convention favors bumping the patch version over deleting tags; release creation is retag-safe but partially-published artifacts from the failed run must be accounted for.
Watch loop after pushing a fix (jobs run 10–60 min; poll accordingly):
watch_pipeline() {
curl -s $H "$API/pipelines/$1" | jq -r '"\(.status) \(.web_url)"'
curl -s $H "$API/pipelines/$1/jobs?per_page=100" \
| jq -r '.[] | "\(.status | .[0:7])\t\(.stage)\t\(.name)"' | sort
}
Pre-tag checklist (run when asked for a "pre-tag check" or before /release tags)
- [ ]
just checkgreen locally - [ ] Last main pipeline fully green (not "green with failed-but-allowed jobs you care about")
- [ ]
.gitlab-ci.ymldiff since the last tag reviewed against the rules above (git diff $(git describe --tags --abbrev=0) -- .gitlab-ci.yml ci/) - [ ] Any NEW tag-only job since the last tag has been exercised (Step 4)
- [ ]
release:gate.needsstill lists every test job except the threeallow_failurereporters (test:audit,test:localization-audit,test:quality-report), non-blocking by design (grep -A30 'release:gate' ci/publish.yml) - [ ]
test:auditwas yellow because it FOUND something, not because it never ran — its log has noAUDIT-DID-NOT-RUNline (allow_failurerenders both the same) - [ ] Updater artifacts: signing is the LAST touch on bytes (mesa patch / notarize / Authenticode
all happen before
.sig) — seedocs/release/updater.md
Report format
State: pipeline + failing job(s) with links · the class from the taxonomy · root cause (quote the log line) · the fix and which hard rule it enforces · how it was verified (pipeline link or local run output) · any follow-up risk (e.g. "publish:ios remains unexercised until next tag").
From the file to a first result
The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.
View the source and supporting filesThis download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.
1. Download the file
Download SKILL.mdFrom the root of a test project, save the file as .skill-downloads/gitlab-ci-doctor-mvj1oc4k/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.
Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.
mkdir -p '.skill-downloads/gitlab-ci-doctor-mvj1oc4k' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/gitlab-ci-doctor-mvj1oc4k/download' --output '.skill-downloads/gitlab-ci-doctor-mvj1oc4k/SKILL.md.part' && mv '.skill-downloads/gitlab-ci-doctor-mvj1oc4k/SKILL.md.part' '.skill-downloads/gitlab-ci-doctor-mvj1oc4k/SKILL.md'2. Check prerequisites
Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.
Tools declared by the author (check availability in your agent): Bash, Read, Edit, Grep, Glob
No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.
The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.
3. Load the skill
After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.
Claude Code
test -f '.skill-downloads/gitlab-ci-doctor-mvj1oc4k/SKILL.md' && test -s '.skill-downloads/gitlab-ci-doctor-mvj1oc4k/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/ci-doctor' && cp -R '.skill-downloads/gitlab-ci-doctor-mvj1oc4k' '.claude/skills/ci-doctor'OpenSkills
OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.
Official OpenSkills installationopenskills install './.skill-downloads/gitlab-ci-doctor-mvj1oc4k'openskills read 'gitlab-ci-doctor-mvj1oc4k'Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.
For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.
openskills syncManual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.
4. Try a first task
Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:
Read .skill-downloads/gitlab-ci-doctor-mvj1oc4k/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.
Configure a Chat Channel with SecretRefs
DevOps
Set up chat channels using non-interactive commands, with secrets only as SecretRefs. Covers gather, mutate, repair, and prove steps for reliable delivery.
Investigate ClickHouse CI Failures
DevOps
Investigate a ClickHouse CI failure end-to-end from a PR or S3 report URL. Fetches failed tests, classifies flaky vs real regression, and checks for existing issues/fixes.
Ansible PR Review
DevOps
Reviews an Ansible PR following the project's standardized process, including changelog checks, test coverage validation, and feedback tracking.