How to fix .gitlab-ci.yml: jobs:build config should be a hash in Git

GitBEGINNERMEDIUMUpdated Dec 6, 2025ยท 207 views

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.

Quick fix

Fixes .gitlab-ci.yml: jobs:build config should be a hash

# 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"
  1. Use GitLab's CI Lint tool to validate your YAML
  2. Ensure jobs are defined as YAML mappings
  3. Fix YAML indentation issues
  4. Check for hidden job definitions under wrong keys
git โ€” error
# 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"
.gitlab-ci.yml: jobs:build config should be a hash

What this error means

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.

Typical symptoms

  • Pipeline fails to start with YAML syntax error
  • GitLab UI shows '.gitlab-ci.yml: jobs:build config should be a hash'
  • CI/CD lint tool reports configuration error
  • Pipeline status shows 'failed' immediately without running any jobs
  • Merge request shows CI error before any jobs execute
  • Error appears after editing .gitlab-ci.yml file

Common causes

  1. Job defined as a scalar value instead of a mapping (most common)
  2. Empty job definition with no configuration under it
  3. Incorrect YAML indentation breaking the structure
  4. Missing 'script' key or other required job configuration
  5. Tabs used instead of spaces in YAML file
  6. Copy-paste error introducing invisible characters
  7. Job configuration placed at wrong indentation level
  8. Anchor/alias YAML syntax used incorrectly

How to fix ".gitlab-ci.yml: jobs:build config should be a hash"

1Use GitLab's CI Lint tool to validate your YAML

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:

bash
# 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:

bash
# If you have the gitlab-ci-local tool installed
gitlab-ci-local --lint
2Ensure jobs are defined as YAML mappings

Each job must be a mapping (hash) with key-value pairs. Fix common incorrect patterns:

Incorrect - scalar value:

yaml
build: npm run build

Incorrect - empty job:

yaml
build:
test:
  script: npm test

Correct - job as mapping:

yaml
build:
  script:
    - npm run build

test:
  script:
    - npm test

Every job needs at least a script key (unless using trigger for child pipelines):

yaml
build:
  stage: build
  image: node:18
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
3Fix YAML indentation issues

YAML is indentation-sensitive. Use consistent 2-space indentation:

Incorrect - inconsistent indentation:

yaml
build:
script:           # Should be indented
  - npm run build

Incorrect - tabs instead of spaces:

yaml
build:
	script:         # Tab character (invisible problem)
		- npm run build

Correct:

yaml
build:
  script:         # 2 spaces
    - npm run build

Configure 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:

json
{
  "[yaml]": {
    "editor.insertSpaces": true,
    "editor.tabSize": 2,
    "editor.renderWhitespace": "all"
  }
}
4Check for hidden job definitions under wrong keys

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:

yaml
jobs:             # 'jobs' is not a GitLab CI keyword
  build:
    script:
      - npm run build

Incorrect - job at wrong level:

yaml
stages:
  - build
  build:          # Accidentally indented under stages
    script:
      - npm run build

Correct:

yaml
stages:
  - build

build:            # Job at root level
  stage: build
  script:
    - npm run build

Reserved keywords that are NOT jobs:
- stages, variables, default, include, workflow, image, services, before_script, after_script, cache

5Validate job structure with proper keys

Ensure each job has valid configuration keys. Common job structure:

yaml
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

6Handle YAML anchors and aliases correctly

If using YAML anchors for reusable configuration, ensure they're defined properly:

Incorrect - anchor without proper structure:

yaml
.build_template: &build_template
  npm run build    # Scalar, not a hash

build:
  <<: *build_template

Correct - anchor with hash structure:

yaml
.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 script

Hidden jobs (starting with .) are templates that aren't run directly but can be extended:

yaml
.common:
  image: node:18
  cache:
    paths:
      - node_modules/

build:
  extends: .common          # Modern way to inherit
  script:
    - npm run build

Advanced notes

### Debugging Complex YAML Structures

For complex configurations, use a YAML parser to validate syntax before GitLab validation:

bash
# 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:

yaml
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:

yaml
trigger_child:
  trigger:
    include: child-pipeline.yml
    strategy: depend

Not:

yaml
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:

json
{
  "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):

yaml
jobs:
  build:
    runs-on: ubuntu-latest

GitLab CI (valid):

yaml
build:
  image: ubuntu:latest
  script:
    - echo "building"

GitLab CI doesn't have a jobs: wrapper key - jobs are defined at the root level.

Frequently asked questions

What does ".gitlab-ci.yml: jobs:build config should be a hash" mean?
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.
How do I fix ".gitlab-ci.yml: jobs:build config should be a hash"?
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.
What causes ".gitlab-ci.yml: jobs:build config should be a hash"?
Job defined as a scalar value instead of a mapping (most common) Empty job definition with no configuration under it Incorrect YAML indentation breaking the structure
Is the ".gitlab-ci.yml: jobs:build config should be a hash" error serious?
It is a medium-severity error โ€” more of a blocker than a risk. The steps above resolve it.

Official resources & further reading