GitLab CI Error Guide: 'chosen stage does not exist' — Fix Stage Ordering
Fix 'chosen stage does not exist' in GitLab CI: declare every job stage in the top-level stages list, match names exactly, and order stages so jobs resolve cleanly.
- #gitlab
- #ci-cd
- #troubleshooting
- #errors
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 pipeline parser rejects the entire .gitlab-ci.yml before a single job runs when a job references a stage: that is not declared in the top-level stages: list. The pipeline never starts; the MR shows a config error instead of a running pipeline:
Found errors in your .gitlab-ci.yml:
jobs:deploy job: chosen stage does not exist; available stages are .pre, build, test, .post
Because this is a validation error, no jobs execute at all — one mistyped stage name blocks the whole pipeline, not just the offending job.
Symptoms
- The pipeline is marked failed immediately with a YAML/config error, no jobs created.
- The message lists the “available stages” — the exact set from your
stages:block plus the implicit.preand.post. - A job you recently added or renamed is the one referenced in the error.
- CI Lint flags the job as invalid while the rest of the file looks fine.
- After an
include:refactor, a job from an included file references a stage the parent never declared.
Common Root Causes
- Stage not listed — the job’s
stage:value is spelled correctly but was never added to the top-levelstages:list. - Name mismatch / typo —
stage: deployvs.stages: [..., deployment], or a trailing space / different case. - Included file assumes a stage — an
included template uses a stage name the including project didn’t declare. - Relying on defaults after customizing — once you define a custom
stages:list, the built-inbuild/test/deploydefaults no longer exist unless you re-list them. .pre/.postconfusion — trying to use a custom stage where only the reserved.pre/.postare appropriate, or misspelling them (pre,.Pre).- YAML anchor/extends drift — a job inherits
stage:from a template that points at a stage removed during a refactor.
Diagnostic Workflow
First validate the config with the CI Lint API, which reports the same parser error locally:
# Validate .gitlab-ci.yml via CI Lint API (replace HOST, TOKEN, PROJECT_ID)
lint-check:
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 run the fully-merged validation (expands include: and extends:) with glab:
# Locally, using the glab CLI — resolves includes before checking stages
# glab ci lint .gitlab-ci.yml
Then list every stage each job references and compare to the declared list:
stages:
- build
- test
- deploy # every job's `stage:` MUST appear here
deploy-prod:
stage: deploy # matches the list exactly — no typo, no case drift
script:
- ./deploy.sh
If the offending job comes from an include, view the merged YAML in the pipeline editor’s “Full configuration” tab to see the effective stages: and every stage: reference in one place.
Example Root Cause Analysis
A team split their pipeline into templates with include:. The main file declared:
stages: [build, test]
include:
- local: ci/deploy.yml
The included ci/deploy.yml contained:
deploy-staging:
stage: deploy # <-- 'deploy' was never added to the parent stages list
script: ./deploy.sh staging
The pipeline failed instantly with chosen stage does not exist; available stages are .pre, build, test, .post. The included template assumed a deploy stage that the parent project never declared. Because included files share the parent’s single stages: list, the fix was to add deploy to the top-level list:
stages: [build, test, deploy]
After adding the stage, the pipeline parsed and the deploy job scheduled in the correct order. The team also added a CI Lint step to their pre-merge checks so a future template referencing an undeclared stage is caught before merge.
Prevention Best Practices
- Declare a complete top-level
stages:list up front and treat it as the single source of truth every job must match. - Run CI Lint (API or
glab ci lint) in a pre-merge job so undeclared stages fail at review, not on the default branch. - When using
include:, keep thestages:list in the parent and document which stages templates may use. - Avoid re-typing stage names in each job — reference a small, known set and use
extends:templates that point at declared stages. - Use the pipeline editor’s “Full configuration” view after any
include/extendsrefactor to confirm everystage:resolves. - Prefer lowercase, hyphen-free stage names consistently to eliminate case/typo mismatches.
Quick Command Reference
# Validate the merged config locally
glab ci lint .gitlab-ci.yml
# Validate via CI Lint API (with include expansion)
curl --header "PRIVATE-TOKEN: $TOKEN" \
"https://gitlab.example.com/api/v4/projects/$PROJECT_ID/ci/lint" \
--data-urlencode "content=$(cat .gitlab-ci.yml)" \
--data "include_merged_yaml=true"
# Grep every stage reference and compare to the declared list
grep -nE '^\s*stage:' .gitlab-ci.yml ci/*.yml
Conclusion
chosen stage does not exist is a pure configuration error: a job points at a stage the top-level stages: list never declared, usually from a typo, a case mismatch, or an included template assuming a stage the parent didn’t add. Because it blocks the whole pipeline, fix it by making the declared stages: list complete and exact, then guard it with a CI Lint step so the next undeclared stage is caught before merge instead of on your default branch.
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.