This GitLab CI error occurs when a job in your .gitlab-ci.yml file is not properly defined as a YAML mapping (hash). The job configuration must be a key-value structure, not a scalar value or list. Fix by ensuring each job has properly indented configuration options.
When GitLab CI parses your `.gitlab-ci.yml` file and encounters this error, it means a job definition is malformed. In YAML terminology, a "hash" (also called a mapping or dictionary) is a collection of key-value pairs. Each job in GitLab CI must be defined as a hash containing configuration options like `script`, `stage`, `image`, etc. The error typically appears when: - A job is defined as a scalar value instead of a mapping - The job name exists but has no configuration under it - YAML indentation is incorrect, causing the parser to misinterpret the structure - A job key is followed by a colon but nothing else, or followed by a list directly For example, this is invalid: ```yaml build: # Job exists but is empty or scalar # or build: "npm run build" # Scalar value instead of hash ``` The correct structure requires key-value pairs under the job name: ```yaml build: # Job name script: # Configuration key - npm run build # Configuration value ``` GitLab CI validates your YAML before running pipelines. This validation catches structural errors early, preventing failed pipeline attempts.
Before pushing changes, validate your .gitlab-ci.yml using GitLab's built-in linter:
1. Go to your GitLab project
2. Navigate to Build > Pipeline editor (or CI/CD > Editor)
3. Paste your .gitlab-ci.yml content
4. Click Validate or look for real-time validation feedback
You can also use the CI Lint API:
# Using the CI Lint endpoint
curl --request POST \
--header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-Type: application/json" \
--data '{"content": "'"$(cat .gitlab-ci.yml)"'"}' \
"https://gitlab.com/api/v4/projects/<project_id>/ci/lint"Or use the local GitLab CI lint if available:
# If you have the gitlab-ci-local tool installed
gitlab-ci-local --lintEach job must be a mapping (hash) with key-value pairs. Fix common incorrect patterns:
Incorrect - scalar value:
build: npm run buildIncorrect - empty job:
build:
test:
script: npm testCorrect - job as mapping:
build:
script:
- npm run build
test:
script:
- npm testEvery job needs at least a script key (unless using trigger for child pipelines):
build:
stage: build
image: node:18
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/YAML is indentation-sensitive. Use consistent 2-space indentation:
Incorrect - inconsistent indentation:
build:
script: # Should be indented
- npm run buildIncorrect - tabs instead of spaces:
build:
script: # Tab character (invisible problem)
- npm run buildCorrect:
build:
script: # 2 spaces
- npm run buildConfigure your editor to:
- Show whitespace characters
- Use spaces instead of tabs
- Set tab width to 2 spaces for YAML files
In VS Code, add to .vscode/settings.json:
{
"[yaml]": {
"editor.insertSpaces": true,
"editor.tabSize": 2,
"editor.renderWhitespace": "all"
}
}Jobs in GitLab CI are top-level keys that don't start with a dot (.) and aren't reserved keywords. Ensure your job isn't accidentally nested:
Incorrect - job nested under non-existent key:
jobs: # 'jobs' is not a GitLab CI keyword
build:
script:
- npm run buildIncorrect - job at wrong level:
stages:
- build
build: # Accidentally indented under stages
script:
- npm run buildCorrect:
stages:
- build
build: # Job at root level
stage: build
script:
- npm run buildReserved keywords that are NOT jobs:
- stages, variables, default, include, workflow, image, services, before_script, after_script, cache
Ensure each job has valid configuration keys. Common job structure:
build:
stage: build # Which stage to run in
image: node:18 # Docker image to use
variables: # Job-specific variables
NODE_ENV: production
before_script: # Commands before main script
- npm ci
script: # Main commands (required*)
- npm run build
after_script: # Commands after main script
- echo "Build complete"
artifacts: # Files to save
paths:
- dist/
expire_in: 1 week
cache: # Files to cache
paths:
- node_modules/
rules: # When to run this job
- if: $CI_COMMIT_BRANCH
tags: # Runner tags
- docker*The script key is required for regular jobs. Exceptions:
- Jobs with trigger: for child pipelines
- Jobs with extends: that inherit script from another job
If using YAML anchors for reusable configuration, ensure they're defined properly:
Incorrect - anchor without proper structure:
.build_template: &build_template
npm run build # Scalar, not a hash
build:
<<: *build_templateCorrect - anchor with hash structure:
.build_template: &build_template
image: node:18
before_script:
- npm ci
script:
- npm run build
build:
<<: *build_template # Merge anchor
stage: build
build_prod:
<<: *build_template
stage: deploy
script:
- npm run build:prod # Override scriptHidden jobs (starting with .) are templates that aren't run directly but can be extended:
.common:
image: node:18
cache:
paths:
- node_modules/
build:
extends: .common # Modern way to inherit
script:
- npm run build### Debugging Complex YAML Structures
For complex configurations, use a YAML parser to validate syntax before GitLab validation:
# Python
python -c "import yaml; yaml.safe_load(open('.gitlab-ci.yml'))"
# Ruby
ruby -ryaml -e "YAML.load_file('.gitlab-ci.yml')"
# Node.js (with js-yaml)
npx js-yaml .gitlab-ci.yml### Common Error Variations
The error message may specify different job names:
- jobs:test config should be a hash
- jobs:deploy config should be a hash
- jobs:build:script config should be a hash
The pattern is always: a key that should be a mapping is something else.
### GitLab CI Include Gotchas
When using include: to import external configurations, errors can come from included files:
include:
- local: '/.gitlab/ci/build.yml'
- template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml'If the error persists after fixing your main file, check included files for the same issues.
### Multi-Project Pipelines
When triggering child pipelines, ensure the trigger job is correctly formatted:
trigger_child:
trigger:
include: child-pipeline.yml
strategy: dependNot:
trigger_child: child-pipeline.yml # Invalid### VS Code Extensions for GitLab CI
Install these extensions to catch errors early:
- GitLab Workflow: Official GitLab extension with CI/CD lint
- YAML: Red Hat's YAML extension with schema validation
Add GitLab CI schema for better validation:
{
"yaml.schemas": {
"https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json": ".gitlab-ci.yml"
}
}### Migrating from Other CI Systems
If converting from other CI systems, note the structural differences:
GitHub Actions (valid):
jobs:
build:
runs-on: ubuntu-latestGitLab CI (valid):
build:
image: ubuntu:latest
script:
- echo "building"GitLab CI doesn't have a jobs: wrapper key - jobs are defined at the root level.
kex_exchange_identification: Connection closed by remote host
Connection closed by remote host when connecting to Git server
fatal: unable to access: Proxy auto-configuration failed
How to fix 'Proxy auto-configuration failed' in Git
fatal: unable to access: Authentication failed (proxy requires basic auth)
How to fix 'Authentication failed (proxy requires basic auth)' in Git
fatal: unable to access: no_proxy configuration not working
How to fix 'no_proxy configuration not working' in Git
fatal: unable to read tree object in treeless clone
How to fix 'unable to read tree object in treeless clone' in Git