Contribution Pipeline (worker skill)
The authoritative final gate. Local verification (contribution-verify) is the fast
inner loop; the real GitLab MR pipeline is the truth. A contribution is not done
until the real drupalci pipeline is green.
Backs /drupal-ai-contrib:pipeline. Load the knowledge layer via dev-guides-navigator:
drupal/contributing/drupalci-pipeline-gitlab-templates,
drupal/contributing/reproducing-drupalci-failures-locally.
Procedure
1. Resolve the MR and project
Identify the GitLab project and merge-request for the current issue fork (from submit
state, or ask). Before any API call, validate against an explicit pattern — the MR
number must match ^[0-9]+$, the project path ^[A-Za-z0-9_./-]+$ — and reject
anything that does not match rather than calling the API with it.
2. Fetch the pipeline — glab via the drupal-gitlab skill
Fetch the MR's latest pipeline and its jobs with glab, delegated to the
drupal-gitlab skill (it owns the git.drupalcode.org auth + host rules; see its
references/ci-cd.md). This is the produced artifact — the actual pipeline result, not a
prediction:
glab ci status # pipeline status for the MR's branch
glab ci view # per-job overview
glab ci trace <job-name> # stream a job's full log (debug failures)
Always pass --repo "git.drupalcode.org/project/<repo>" (or run inside the configured
git dir) — never rely on the default host. Credentials are the contributor's own (a
read-only token suffices for status); never store or transmit them.
Two hard limits on git.drupalcode.org:
- Pipelines fire on push events only — API/CLI triggers (
glab ci run,POST /pipeline) are blocked. To re-run CI, push a commit (an--allow-emptycommit works); never claim a re-trigger you cannot perform. - Never WebFetch a GitLab job/MR URL — the pages are JavaScript-rendered and return
no log. Read job logs with
glab ci trace <job>(orglab api … /jobs/<id>/trace).
3. Read the pipeline honestly
A pipeline reported "passed" can still hide problems. Inspect every job:
allow_failurejobs — a redallow_failurejob does not fail the pipeline but is still a real failure. Surface it.- Manual / opt-in jobs —
gitlab_templatesv1.15.0+ moved opt-in variant jobs (OPT_IN_TEST_PREVIOUS_MAJOR,_PREVIOUS_MINOR,_NEXT_MINOR,_MAX_PHP) to manual trigger. An un-run manual job is not coverage. Report it explicitly as not run — never imply a green pipeline means those variants passed.
A failed eslint job ships its own fix — stylelint does not. The shared
gitlab_templates eslint job always uploads _eslint.patch as a job artifact (empty
when there's nothing to fix) — the exact diff its own --fix run produced. Get the job
ID from glab ci view, then fetch and apply the patch directly:
glab api …/jobs/<id>/artifacts/_eslint.patch > _eslint.patch
git apply _eslint.patch
stylelint publishes findings only (gl-codequality.json), never a patch — there is
nothing to fetch and apply. Read the report, or reproduce stylelint --fix locally
against whichever config actually applies (project's own, or core's only if absent).
Never re-derive an eslint fix by running --fix on the whole project "to see what
breaks". The job runs --fix . across the whole project, so its patch is not narrow
by scoping; it is narrow because the project was already clean apart from the change. A
local --fix on a project that has drifted since reformats every drifted file at once,
most of them outside the current contribution. The patch artifact is the safe path
precisely because it is the diff the job produced against that state, not one you
regenerate against yours. If _eslint.patch is unavailable, scope any local --fix to the single
failing file and run git diff --stat immediately after to catch an abnormal blast
radius before committing.
4. Gate
The contribution is done only when: every blocking job is green, every
allow_failure job's real status is surfaced, and every relevant manual variant is
either triggered-and-green or explicitly recorded as a deliberate non-goal.
If a job failed, diagnose it against the reproduction dev-guide and route the
contributor back to development → verify. Do not mark the work complete on local
green alone.
5. Report
Summarize per job: name, status, blocking vs. allow_failure vs. manual, and the link
to the job log. State plainly: is the contribution done, or what remains. The verdict
is the pipeline's — never assert "should pass".
Examples
Example 1: a green pipeline hiding a red allow_failure job
Trigger: /drupal-ai-contrib:pipeline
Actions:
- Fetch the MR pipeline — overall status "passed".
- Inspect every job —
phpstanis red butallow_failure: true. Result: Reported: pipeline green, butphpstanfailed (non-blocking) — surfaced, not hidden.
Example 2: un-run manual variant
Trigger: /drupal-ai-contrib:pipeline
Actions:
- All blocking jobs green; the
PREVIOUS_MAJORvariant is a manual job, never triggered. Result: Reported as UNRUN — not implied coverage; the contributor decides whether to trigger it.
Troubleshooting
| Situation | Handling |
|---|---|
| No MR found for the branch | Report it; point at /drupal-ai-contrib:submit to create the MR first. |
glab unauthenticated / API unreachable |
Report the failure (glab auth login --hostname git.drupalcode.org); never substitute a local-green result for the real pipeline. |
| Pipeline still running | Report in-progress; the gate is not satisfied until jobs finish. |
| Pipeline needs re-running | Triggers are blocked on git.drupalcode.org — push a commit (--allow-empty if no code change); pipelines fire on push only. |
| A blocking job failed | Diagnose against the reproduction dev-guide; route back to development → verify. |
eslint job failed, local run was clean |
Local eslint likely isn't using core's linked config + pinned binary (contribution-verify §1) — don't re-guess; fetch _eslint.patch from the job instead. |
stylelint job failed |
No patch artifact exists for stylelint — read gl-codequality.json from the job, or reproduce stylelint --fix locally scoped to the failing file(s). |