Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for GitLab CI/CD By James Joyner IV · · 8 min read

GitLab CI Error Guide: 'found duplicate key' — Fix Repeated YAML Keys

Quick answer

Fix 'found duplicate key' in GitLab CI: a mapping defines the same key twice, so YAML drops one. Find the repeated key, merge the blocks, and re-lint the file.

  • #gitlab
  • #ci-cd
  • #troubleshooting
  • #errors
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

Overview

GitLab’s YAML parser rejects .gitlab-ci.yml when the same key appears twice inside one mapping. The pipeline fails to parse and reports the offending key and line:

Found errors in your .gitlab-ci.yml:
  (<unknown>): found duplicate key "script" with value "..." (original value: "...") at line 14 column 3

Some tooling downgrades this to a warning and silently keeps only the last occurrence — which is worse, because the pipeline “works” while ignoring half your config. Either way, a duplicate key means one of your definitions is being dropped.

Symptoms

  • The pipeline fails at parse time with found duplicate key "<name>" and a line/column.
  • A key you set (a variables entry, a script, a rules block) appears to have no effect — the second definition overwrote the first.
  • A job runs only the last script: you wrote, ignoring an earlier one in the same job.
  • Merging two snippets into one job produced two before_script:/cache:/rules: keys.
  • CI Lint flags the duplicate even though each line is individually valid.

Common Root Causes

  • Copy-paste merge — combining two job snippets left two script: (or variables:, rules:, cache:) keys in one mapping.
  • Repeated variable name — the same key defined twice under variables:; only the last value survives.
  • Two default:/global blocks — a second top-level default: or variables: mapping.
  • extends + inline collision authored as literal duplicate — writing the key twice instead of relying on override semantics.
  • Anchor plus explicit key — a merged anchor (<<: *base) and an explicit key that the author also duplicated literally.
  • Manual indentation edit — a block accidentally split so a key is re-declared at the same level.

Diagnostic Workflow

Reproduce the exact parser error locally with any strict YAML loader — GitLab uses strict duplicate-key detection:

# Validate with CI Lint (reports the duplicate key + line)
lint:
  image: curlimages/curl:latest
  script:
    - |
      curl --silent --header "PRIVATE-TOKEN: $LINT_TOKEN" \
        "https://$CI_SERVER_HOST/api/v4/projects/$CI_PROJECT_ID/ci/lint" \
        --data-urlencode "content=$(cat .gitlab-ci.yml)"

Or catch it in a pre-commit check with a strict parser that fails on duplicates:

yaml-strict:
  image: python:3.12
  script:
    - pip install "ruamel.yaml"
    - |
      python - <<'PY'
      from ruamel.yaml import YAML
      YAML(typ="safe").load(open(".gitlab-ci.yml"))   # raises on duplicate keys
      print("no duplicate keys")
      PY

Then locate the repeated key and decide whether to merge the two blocks or rename one:

# BEFORE — duplicate 'script' in one job; the first is silently dropped
build:
  script:
    - make deps
  script:                 # <-- duplicate key
    - make build

# AFTER — one script listing both commands in order
build:
  script:
    - make deps
    - make build

Example Root Cause Analysis

A developer added caching to an existing job by pasting a snippet from documentation. The job ended up with two variables: mappings:

test:
  variables:
    DATABASE_URL: "postgres://ci@db/test"
  variables:                          # <-- second 'variables' key
    RAILS_ENV: "test"
  script:
    - bundle exec rspec

CI Lint reported found duplicate key "variables". On a lenient parser the job had actually been running with only the second block — so DATABASE_URL was silently missing and the test suite had been connecting to the wrong database, a bug that had confused the team for days. The fix merged both entries into a single mapping:

test:
  variables:
    DATABASE_URL: "postgres://ci@db/test"
    RAILS_ENV: "test"
  script:
    - bundle exec rspec

With one variables: mapping, both values applied and the suite connected correctly. The team added the strict-YAML pre-commit check so a future duplicate key fails locally instead of silently dropping config.

Prevention Best Practices

  • Keep exactly one of each mapping key per job/scope — one script:, one variables:, one rules:, one cache:; list multiple items as sequence entries under that single key.
  • Run CI Lint or a strict YAML loader (ruamel/yamllint with duplicate-keys enabled) in a pre-merge check so duplicates fail fast.
  • When combining snippets, merge into the existing key rather than pasting a second one.
  • Use extends:/anchors for shared config instead of literally repeating keys; understand override semantics rather than duplicating.
  • Enable yamllint in your editor to highlight duplicate keys as you type.
  • Review indentation after manual edits so a block isn’t accidentally re-declared at the same level.

Quick Command Reference

# yamllint with duplicate-key detection
yamllint -d "{rules: {key-duplicates: enable}}" .gitlab-ci.yml

# Strict parse that raises on duplicates
python -c "from ruamel.yaml import YAML; YAML(typ='safe').load(open('.gitlab-ci.yml')); print('ok')"

# Validate via GitLab CI Lint
glab ci lint .gitlab-ci.yml

# Quickly spot repeated top-level-ish keys in one job (manual scan)
grep -nE '^\s{2,4}(script|variables|rules|cache|before_script):' .gitlab-ci.yml

Conclusion

found duplicate key means one mapping defines the same key twice, so YAML keeps only the last and drops the rest — sometimes failing the pipeline, sometimes silently discarding half your config. Find the repeated key with CI Lint or a strict YAML parser, merge the two blocks into a single key with a proper sequence, and add a duplicate-key linter to your pre-merge checks so the next accidental repeat fails loudly instead of quietly changing behavior.

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.