← Monitoring guides

By PingCron · Updated

GitLab Scheduled Pipeline Not Running? Check Ownership, Rules and Runners

When a GitLab scheduled pipeline stops running, first find the last object that actually exists. An inactive schedule, an absent pipeline, an excluded job and a pending job are different failures. Changing the cron expression will not fix all four.

A particularly confusing case is a schedule that works when you press Run, then misses its next automatic occurrence. That test may have changed the identity running the pipeline. Start with evidence from the missed occurrence, rather than treating the successful manual run as proof that scheduling is healthy.

This guide covers ordinary project pipeline schedules. GitLab's separate security-policy scheduling features have different configuration and limits; use their scheduled pipeline execution policy documentation when that is what you operate.

Start with the last object that exists

Record the expected occurrence, including its timezone, before investigating. Compare the configured timezone, displayed next occurrence and the instance's maximum schedule frequency with what you expected. Then follow this table from top to bottom.

What you can observeCheck nextWhat the observation does not prove
Schedule is inactiveOwner availability, access and namespace restrictionsReactivating it does not prove a completed run
Schedule is active, but no pipeline exists for that occurrenceTarget ref, timing, owner permissions and workflow rulesA successful manual run may use another person's permissions
Pipeline exists, but the required job is absentJob rules and included configurationOther green jobs do not establish that this job ran
Required job exists and is pendingEligible runners, tags, protection and capacityA different cron expression does not supply a runner
Job succeeded, but expected data is staleApplication logic and destination validationExit zero is not evidence of fresh business output
Output was validated, but no completion receipt arrivedReporting configuration, connectivity and receipt historyMissing telemetry does not identify the cause

Keep the pipeline ID and job ID with your notes. A green pipeline from yesterday, a push pipeline from this morning and the scheduled occurrence you are investigating are not interchangeable evidence.

Avoid immediately deleting and recreating the schedule. That changes the object you are trying to understand and can hide an ownership or configuration problem without explaining it. Preserve enough context to compare the failed occurrence with the next controlled test.

1. Check the schedule owner, not only your own access

GitLab runs an automatic scheduled pipeline with its owner's permissions. Running that schedule manually uses the permissions of the person who triggers it. A successful manual test can therefore conceal an owner-access problem.

Open Build > Pipeline schedules and inspect the owner and target branch or tag. A blocked owner or one removed from the project can leave the schedule inactive. Protected refs also impose access requirements. An authorized Maintainer or Owner can take ownership when needed. Check the relevant permissions instead of temporarily removing branch protection. GitLab schedule ownership and manual execution

After an authorized ownership change, observe an actual automatic occurrence. Keep the manual test and the automatic test as separate entries in your incident notes.

For example, if a maintainer can run a schedule that belongs to a departed colleague, the result establishes that the maintainer could initiate that run. It does not establish that the old owner's next timed attempt will be authorized.

Also check whether the schedule targets the ref you are inspecting. Editing CI configuration on one branch does not change the configuration on every branch.

GitLab.com: restored namespaces can still have inactive schedules

GitLab documents another cause specific to GitLab.com: a schedule due while its namespace is read-only can be deactivated. Restoring the namespace does not automatically restore those schedules. An authorized operator must address the namespace restriction, identify the affected schedules and reactivate the intended ones. GitLab support explanation

Do not bulk-enable every inactive schedule. Some may have been deliberately stopped. Establish ownership and purpose before restoring work, especially if it sends messages, charges accounts or changes production data.

2. Separate pipeline creation from job inclusion

A schedule can reach the configuration layer and still produce none of the work you expected. There are two distinct gates:

  1. workflow:rules controls whether the pipeline is created.
  2. A job's rules controls whether that job is included.

GitLab evaluates workflow before jobs. A schedule rule added to a job cannot rescue a pipeline rejected by workflow. A workflow that only accepts CI_PIPELINE_SOURCE == "push" excludes schedules. GitLab workflow evaluation

Within a job's rules, the first matching rule decides inclusion or exclusion. Check earlier exclusions and configuration brought in through include, rather than reviewing only the last rule you added. GitLab job rules

Do not repair the problem with a blanket when: always unless you intend to admit all the additional pipeline sources it allows. Explicitly preserve your push, merge-request, tag and schedule behavior.

A harmless schedule-only probe

The following is a complete teaching configuration for a disposable test project. It prints non-secret context and performs no business operation. It deliberately excludes other pipeline sources. Do not replace an existing production configuration with it.

Use a runner that can execute this Linux container job and is eligible for untagged jobs. If your test environment requires tags, select the actual approved runner's tags.

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
    - when: never

stages:
  - verify

schedule_probe:
  stage: verify
  image: alpine:3.22
  retry: 0
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
  script:
    - |
      printf 'source=%s ref=%s pipeline=%s\n' \
        "$CI_PIPELINE_SOURCE" \
        "$CI_COMMIT_REF_NAME" \
        "$CI_PIPELINE_ID"

Create a test schedule for the ref containing this configuration. Choose a permitted interval and record its timezone and next expected occurrence. Wait for the automatic occurrence and inspect the resulting pipeline and job.

The expected log contains source=schedule plus the test ref and pipeline ID. Those values provide a useful correlation record. They do not, by themselves, distinguish clicking Run on a schedule from waiting for its timed occurrence; record how you initiated the test.

The template is a diagnostic starting point, not a claim that PingCron ran a GitLab-hosted experiment. Validate it in your GitLab version and runner environment. A local YAML parse or successful shell command cannot establish GitLab scheduling behavior.

3. Treat CI Lint as configuration evidence

GitLab's CI Lint can check configuration, including included files. Its documented UI pipeline simulation runs as a push event on the default branch. That is a different context from the schedule-only probe above. GitLab CI Lint documentation

A successful push simulation does not validate the schedule owner's permissions, a non-default target ref, runner availability or the next automatic occurrence. Conversely, a schedule-only configuration can intentionally exclude the push context used by that simulation.

Keep three checks separate:

  • Configuration check: is the file accepted, and what configuration does it include?
  • Manual execution check: can this particular operator initiate the intended work?
  • Automatic occurrence check: did this schedule create the expected pipeline and job at the expected time?

When these disagree, preserve the evidence instead of repeating the same successful test. Record the ref, configuration revision, initiating identity and source for each run. That makes an access mismatch or rule mismatch easier to explain to the project maintainer.

Never paste a full environment dump into a public issue to show what happened. Collect only the fields needed for diagnosis and remove secrets, private project details and sensitive output.

4. If the job is pending, investigate runners

Once the required job exists, pipeline creation and job inclusion have happened. The investigation has moved downstream.

Check the job's runner message and requested tags. A runner needs every tag requested by the job, along with suitable project scope and protection settings. Untagged jobs need a runner configured to accept them. An eligible runner also needs capacity to take work. GitLab runner configuration

Compare a pending scheduled job with a recently executed job only after checking that their ref, tags and runner eligibility match. A green job assigned to a different runner pool is weak evidence for this one.

Avoid removing protection or adding arbitrary tags until something starts. Tags describe where a workload is allowed to run; changing them can send it to an unsuitable execution environment.

If the job starts but cannot fetch its image, dependencies or credentials, use that job's logs and environment-specific configuration. Those failures are different from a pipeline that never existed. Preserve that distinction in alerts and incident titles.

5. Define the result before adding a completion signal

A scheduled export might successfully start, execute and exit while leaving yesterday's file at the destination. A backup process can produce an archive without proving that it can be restored. Decide what a meaningful successful occurrence must establish.

Useful validation checks depend on the workload:

  • An export has the expected batch date and required partitions.
  • A sync advanced the destination watermark for this occurrence.
  • A report contains the required inputs and reached its intended destination.
  • A backup completed its integrity checks, with recovery verified by a separate restore drill.

Place a completion signal after the work and its validation. The signal then has a clear meaning: this producer reported that this milestone passed. The receiving service still cannot independently prove the business data is correct.

Use an explicit sequence:

Run the operation
    -> check its exit status
    -> validate the required destination state
    -> send the completion receipt
    -> record reporting failure separately

GitLab's after_script can execute after script failure or cancellation. An unconditional success request there can falsely report completed work. Use the main successful command path for success reporting, and review the exact runner behavior when designing cleanup or failure reporting. GitLab after_script reference

A receipt sent from a job's script also precedes later execution phases such as artifact upload. If uploaded artifacts are the actual deliverable, that earlier signal is insufficient. Likewise, one job's receipt does not establish success of every other job in the pipeline. GitLab job execution flow

Keep reporting failure separate from business-operation failure. If the work already committed, automatically retrying the entire job just to resend telemetry can repeat its side effects. Use an idempotent workload or a deliberate recovery procedure; a heartbeat is not an exactly-once mechanism.

Store a private heartbeat URL using the project's approved secret-management mechanism. Review masked/hidden variable support and protected-ref availability. Masking alone is not a guarantee against disclosure by malicious scripts or debug output. GitLab CI/CD variable security

For the monitoring pattern and its limits, use the cron completion-heartbeat guide. The same principle can fit GitLab jobs without claiming a native GitLab integration.

6. Verify both completion and a missing occurrence

Start with harmless fixture work in a test project and a separate monitor. Match the schedule and timezone, then allow time for legitimate runner queueing, execution, validation and reporting.

Observe one automatic occurrence all the way through:

  1. The intended schedule creates a pipeline.
  2. The required job is present and executes.
  3. The expected fixture output passes validation.
  4. The receiver records the completion signal.
  5. The responsible person can identify that occurrence from the evidence.

Then test the missing-run path. Ensure no active test job can send a late signal, disable only the test schedule, and leave its monitor enabled. After the next expected occurrence and the configured allowance, verify the actual notification destination. Restore the test schedule and verify recovery with another automatic occurrence.

Do not disable a production schedule to perform this exercise. Do not treat a notification preview as proof that a real missing occurrence will reach the responsible person.

If you need an independent expected-completion check, create a PingCron monitor for one task and verify the receipt and alert path. PingCron receives check-ins and can alert when an expected one is missing. It does not run the GitLab pipeline, repair permissions or validate the destination data for you.

Copyable incident worksheet

Use this in an internal incident note. Keep private identifiers and logs out of public bug reports.

Expected occurrence and timezone:
Schedule ID, active state and owner:
Target ref and configuration revision:
Automatic occurrence or manually initiated test:
Pipeline ID and source, or no matching pipeline observed:
Required job ID and state, or job absent:
Runner eligibility/capacity evidence:
Destination invariant and validation result:
Completion receipt time, or no receipt observed:
Last confirmed successful occurrence:
One proposed change and the evidence supporting it:
Next automatic occurrence to verify:

Change one relevant cause at a time where practical. Compare the same boundary before and after the change. If a job goes from absent to pending, the repair may have restored inclusion while exposing a separate runner issue; it has not yet restored completed work.

The stopping point is a verified automatic occurrence with the required result and a tested missing-run notification. A green manual pipeline is useful evidence, but it is not that entire acceptance test.

Related monitoring guides