Jenkins + GitLab webhook — the pollSCM bootstrap trap
Why a freshly-cloned Jenkins pipeline ignores GitLab pushes even when the webhook delivery log shows HTTP 200: declarative-pipeline triggers register at build time, so build #1 has to run manually before notifyCommit resolves the job.
This page documents the non-obvious onboarding gotcha that hits every new Jenkins pipeline cloned from the openliberty-readiness-probe template (and similar declarative Jenkinsfiles) when GitLab webhook delivery looks healthy but no builds fire on push. The fix is two lines in the Jenkinsfile plus one manual build. The trap reproduces on every clone of the template until the template itself is hardened.
Symptom
All of the following are true together:
- A new Jenkins job has been created for a GitLab project.
- The GitLab webhook is configured against the Jenkins
notifyCommitendpoint. - The GitLab webhook event log shows
HTTP 200for pushes. - No corresponding Jenkins build appears in the job history on those pushes.
- Manual builds via the Jenkins UI or API DO work.
If the webhook event log shows anything other than 200, fix the delivery layer first (TLS, hostname resolution, Jenkins reachability, CSRF crumb). This page covers only the case where delivery succeeds but no build fires.
Diagnostic that pinpoints the cause — inspect the registered triggers in the job’s config XML:
curl -s -u "$JENKINS_USER:$JENKINS_TOKEN" \
https://jenkins.sub.comptech-lab.com/job/<name>/config.xml \
| grep -A2 "<triggers>"
- Presence of
<hudson.triggers.SCMTrigger>inside<triggers>meanspollSCMIS registered — this page does not apply. - Absence of
<hudson.triggers.SCMTrigger>(or an empty<triggers/>) means the directive has not been installed yet — this IS the case.
Root cause
Two coupled mechanisms, both required:
-
Jenkins
notifyCommitonly fires for jobs whose declarative pipeline declarestriggers { pollSCM('...') }. Without that block, the webhook delivery is silently ignored even though the URL match against the job’s SCM URL is correct. -
Declarative-pipeline triggers are registered at build time. Jenkins reads the
Jenkinsfileand installs thepollSCMdirective into the job config only AFTER a build has run and parsed the file. So merely addingtriggers { pollSCM(...) }to the repository is not enough — a manual build #1 has to execute first.
The combined effect: a freshly-cloned job from a template without pollSCM looks like a broken webhook on push #1, regardless of how clean the GitLab side is.
Fix
Three steps and a probe.
Step 1 — Add the trigger block to the Jenkinsfile
Place it alongside agent, options, parameters, inside the top-level pipeline { ... }:
pipeline {
agent { label 'docker-runtime' }
triggers { pollSCM('H/5 * * * *') }
options { timestamps() }
stages { /* ... */ }
}
The cron expression H/5 * * * * is a no-op safety net (Jenkins polls every five minutes). The real trigger is notifyCommit from the GitLab webhook. Do not raise the schedule frequency — the fix is the notifyCommit path, not a busy poll.
Step 2 — Commit, push, and run build #1 manually
Push to the project’s default branch. Then trigger build #1 from the UI or API so Jenkins parses the Jenkinsfile and registers the trigger into the job config:
curl -s -X POST -u "$JENKINS_USER:$JENKINS_TOKEN" \
https://jenkins.sub.comptech-lab.com/job/<name>/build
Wait for the build to complete (or at minimum to start and parse the Jenkinsfile).
Step 3 — Confirm the trigger is now registered
curl -s -u "$JENKINS_USER:$JENKINS_TOKEN" \
https://jenkins.sub.comptech-lab.com/job/<name>/config.xml \
| grep -A3 "<triggers>"
Expected output now contains <hudson.triggers.SCMTrigger> with the cron spec.
Step 4 — Validate end-to-end
cd <project-clone>
echo "$(date -u +%FT%TZ)" >> README.md
git commit -am "chore: webhook bootstrap probe"
git push
Within seconds, build #2 should appear in the Jenkins job history listed as triggered by SCM change.
Pre-action diagnostic state
Before fixing, capture:
-
The GitLab project hooks event log:
GLAB=https://gitlab.sub.comptech-lab.com/api/v4 curl -s -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "$GLAB/projects/<project-id>/hooks/<hook-id>/events" | jq '.[0:3]'Confirm recent deliveries return
200. -
The Jenkins job’s
config.xml<triggers>block (shown above). -
The Jenkinsfile’s
pipeline { ... }block — verify it currently lackstriggers { pollSCM(...) }.
Forbidden actions
- Do NOT raise the
pollSCMcron frequency aboveH/5 * * * *as a workaround. The fix isnotifyCommit, not a busy poll. - Do NOT add a second webhook in GitLab pointing at
/job/<name>/build?token=...as a “belt and braces” workaround. It bypasses the trigger model and creates anonymous-build security exposure. - Do NOT remove
triggers { pollSCM(...) }from working pipelines thinking the webhook will keep working without it. Trigger registration is what makesnotifyCommitresolve the job.
Example: liberty-smoke (observed 2026-05-09)
- GitLab event log:
200on every push since the project was created. - Jenkins job history: only manual builds; no SCM-triggered builds.
config.xml:<triggers/>is empty.- Fix: added
triggers { pollSCM('H/5 * * * *') }to the Jenkinsfile, pushed, ran build #1 manually. Build #1’s cause wasStarted by user. - After build #1:
config.xmlshowed<hudson.triggers.SCMTrigger><spec>H/5 * * * *</spec>.... - Pushed a one-line README change. Build #2 fired automatically with cause
Started by SCM change.
Prevention
The trap reproduces on every clone of the openliberty-readiness-probe template until the template Jenkinsfile is hardened. Two parallel changes close the gap at the source:
- Add
triggers { pollSCM('H/5 * * * *') }to the template’s Jenkinsfile so new clones inherit it. - Document the “bootstrap-trigger build #1” step in the developer-handbook day-1 quickstart so first-day developers do not mistake the silent-webhook symptom for a broken pipeline.
Both items are tracked as template-hardening follow-ups under the developer-handbook/ work.
References
opp-full-plat/connection-details/jenkins.md— Jenkins connection details and credential custody.opp-full-plat/runbooks/jenkins-gitlab-webhook-pollscm.md— operator-facing source.opp-full-plat/developer-handbook/— first-day developer quickstart (target page for documenting the bootstrap-build step).- Jenkins
SCMTriggerdocumentation.