Using "extends" and YAML merge not working as expected

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

  • Close this issue

Summary

When mixing "extends" with a YAML merge (<<) in .gitlab-ci.yml, the results are inconsistent (they depend on the order of "extends" and <<") and in no case do they match what I would expect to see.

Steps to reproduce

Run the following .gitlab-ci.yml files through CI Lint in GitLab. NOTE: In my real-world use-case, some of these are pulled in via "include:" which is why I'm using a combination of extends and YAML anchors/merges but the problem is reproducible without "include:" - I've noted those below via comments which are part of an include:, FYI.

The "stage/only" is lost

stages:
  - build
  - test

.build-script-template:  # part of an include:
  before_script:
    - "date"
  script:
    - ':'

.build-template:  # part of an include:
  stage: build
  only:
    - tags

.build-foo-script: &build_foo_script
  extends: .build-script-template
  script:
    - 'bar'

build-foo:
  extends: .build-template
  <<: *build_foo_script

The "before_script" is lost

stages:
  - build
  - test

.build-script-template:  # part of an include:
  before_script:
    - "date"
  script:
    - ':'

.build-template:  # part of an include:
  stage: build
  only:
    - tags

.build-foo-script: &build_foo_script
  extends: .build-script-template
  script:
    - 'bar'

build-foo:
  <<: *build_foo_script
  extends: .build-template

What is the current bug behavior?

With the file 'The "stage/only" is lost', the job "build-foo" is listed by CI Lint at a "test" job with a script containing "date" and "bar" and the Only policy is more than just tags - the stage and "only" clause from .build-template are lost. It results in a structure that is identical to this:

stages:
  - build
  - test

build-foo:
   before_script:
      - date
   script:
      - bar

With the the file 'The "before_script" is lost', the job is listed by CI Lint as a "build" job with a script containing only the "bar" command and an Only policy of tags - the before_script from .build-script-template is lost. It results in a structure that is identical to this:

stages:
  - build
  - test

build-foo:
   only:
     - tags
   script:
     - bar

What is the expected correct behavior?

The expected output from CI Lint in at least one (but probably both) of those cases should be a "build" job script containing "date" and "bar and an Only policy of tags, i.e. the structure should be this:

stages:
  - build
  - test

build-foo:
  stage: build
  only:
    - tags
  before_script:
    - date
  script:
    - bar

Results of GitLab environment info

N/A - I can reproduce this on my Omnibus install (11.4.5) as well as in Gitlab.com.

Results of GitLab application Check

N/A - I can reproduce this on my Omnibus install (11.4.5) as well as in Gitlab.com.

Possible fixes

Unknown.

Edited Jul 21, 2025 by 🤖 GitLab Bot 🤖
Assignee Loading
Time tracking Loading