Breaking Changes: Config Compilation Updates — September 21, 2026

Note: Date changed from August 17 to September 21. We are adding warning messages to the UI, Build Notifications, and API endpoints. When pipelines run that contain deprecated features, a warning message will be returned. APIs impacted will include GET /api/v2/pipelin, GET /api/v2/pipeline/{pipeline-id}, GET /api/v2/project/{project-slug}/pipeline, GET /api/v2/project/{project-slug}/pipeline/mine, and GET /api/v2/project/{project-slug}/pipeline/{pipeline-number}.**

Note:** This post consolidates and replaces three earlier Discuss posts that covered each of these breaking changes separately, each with its own effective date. Those dates have since been aligned — all three changes now take effect on September 21, 2026. Please refer to this post as the single source of truth going forward.

On September 21, 2026, CircleCI is introducing three breaking changes to how configs are compiled. As part of a larger initiative to build next-generation config tooling, stricter and more predictable validation gives us the reliable foundation needed to support more flexible config in the future.

Pipelines affected by any of these changes will fail to compile after this date.


How to check if you’re affected

You can preview whether your config is affected today, before any of these changes are enabled.

  1. Validate your config using either:
    • UI: Open the config editor and enable the “config next” toggle (bottom-right). It will re-validate your config automatically.

    • CLI: Install or update to the latest CircleCI CLI (see Installing the CircleCI local CLI), then run:

      circleci config validate <path-to-config> --next
      
      
  2. Review the errors — the output will surface any issues caused by the three breaking changes below.
  3. Fix them — refer to the relevant section below for guidance on how to resolve each type of error. You can also use an AI agent to help resolve issues quickly.
  4. Revalidate using the same UI toggle or CLI command to confirm all errors are resolved.

What’s changing

Expand each section to understand what’s changing and how to fix it.


1. Undeclared parameters will fail compilation

Background

Pipeline parameters can only be resolved in the config file in which they are declared. Currently, there are niche cases when a reference points to a parameter that isn’t declared or in scope, the config compiles anyway, and the reference silently resolves to null instead of failing. This can lead to unintentional, difficult to troubleshoot behaviour.

A common example

In this example, the config is invalid because orbs cannot use pipeline parameters:

version: 2.1
parameters:
  bug:
    type: string
    default: "this is buggy"
orbs:
  buggy-orb:
    jobs:
      exhibit-bug:
        docker: [image: "python:2.7"]
        parameters:
          bug:
            type: string
            default: << pipeline.parameters.bug >>
        environment:
          FOO: << parameters.bug >>
        steps:
          - checkout
workflows:
  build-test-deploy:
    jobs:
      - buggy-orb/exhibit-bug

Today, this compiles and FOO resolves to null. After the change, it will fail to compile.

How to fix

Remove references to parameters not declared in the current scope, or declare the parameter where it’s used.


2. Regex engine update

The regex engine used to evaluate regex conditions is being updated to improve the security and reliability of config compilation. After September 21, configs using any of the following regex features will fail to compile:

  • Negative lookaheads
  • Negative lookbehinds
  • Possessive quantifiers
  • Backreferences

How to fix

If circleci config validate --next mentioned above surfaces a regex error, find the pattern in your config and rewrite it using the guidance below.

Negative lookaheads (?!...)

Negative lookaheads assert that the string does not match a pattern at the current position. They’re typically used to run a job on every branch except a specific set.

Before (will break):

when:
 matches: { pattern: "/^(?!develop$)(?!build_all$)(?!integration-....?)(?!integration2)(?!fedint-....?)(?!hotfix/.*?/.*$).*/", value: << pipeline.git.branch >> }

This pattern matches any branch except develop, build_all, integration branches, etc.

Fix:

unless: >-
  pipeline.git.branch == "develop" or
  pipeline.git.branch == "build_all" or
  pipeline.git.branch matches /integration-.{3,}/ or
  pipeline.git.branch matches /integration2/ or
  pipeline.git.branch matches /fedint-.{3,}/ or
  pipeline.git.branch matches /hotfix\/.+\/.*/

Before (will break):

when:
  matches: { pattern: "^(?!main).*$", value: << pipeline.git.branch >> }

This matches any branch except main.

Fix:

unless: pipeline.git.branch matches /main.*/

Before (will break):

filters:
  branches:
    only: /^(?!master|staging|release|hotfix).*$/

Matches any branch that does not start with master, staging, release, or hotfix.

Fix:

branches:
  ignore:
    - /master.*/
    - /staging.*/
    - /release.*/
    - /hotfix.*/
Negative lookbehinds (?<!...)

Negative lookbehinds assert that the string does not have a particular substring immediately before the current position. There’s no single-regex equivalent, but you can split the logic into a positive match and a negative exclusion.

Before (will break):

when:
  matches: { pattern: "^@myorg\/[\w-]+(?<!-uat)@\d+\.\d+\.\d+-rc\.\d+$", value: << pipeline.git.tag >> }

This matches npm package version strings like @myorg/some-pkg@1.2.3-rc.4 but excludes packages ending in -uat.

Fix: The recommended approach is a two-step check: match the positive pattern first, then explicitly exclude the -uat variant:

when: >-
  pipeline.git.tag matches /@myorg\/[\w-]+@\d+\.\d+\.\d+-rc\.\d+/ and
  not pipeline.git.tag matches /@myorg\/[\w-]+-uat@.*/

Possessive quantifiers ?+, \*+, ++

Possessive quantifiers match greedily and never backtrack. Simply remove the trailing + to use a plain quantifier — behavior is identical for all practical inputs.

Before (will break):

^staging[0-9]?+$

This matches staging optionally followed by a single digit, possessively.

Fix: Drop the + to use a plain optional quantifier. Behaviour is identical for all practical inputs:

^staging[0-9]?$

Backreferences \\1, \\2, etc.

Backreferences match the same text that was previously captured by a numbered group, letting a single pattern enforce that two substrings are identical. The new engine cannot do this in a single pattern.

Before (will break):

^release/(v\d+\.\d+\.\d+)/\1$

This matches tags like release/v1.2.3/v1.2.3 where the version appears twice and both must be identical.

Fix:

Option 1: Loosen the regex and accept two independent version-like substrings. In practice the tag is produced by release tooling, so a mismatch is unlikely in normal operation:

^release/v\d+\.\d+\.\d+/v\d+\.\d+\.\d+$

Option 2: Enumerate the specific values with a when: or: block, if the set of valid tags is small and known ahead of time.


3. End of support for version: 2.0

After September 21, all pipelines must use version: 2.1. CircleCI has supported two config versions for several years: v2 (legacy) and v2.1 (current). Version 2.1 introduced orbs, pipeline parameters, reusable commands, reusable executors, and a well-defined schema with strict validation. As we build next-generation config tooling, we are consolidating onto v2.1 as the single supported configuration format.

How to fix

Update the version key in your config:

# Before
version: 2.0

# After
version: 2.1

Then run circleci config validate <path-to-config> --next (or use the config editor toggle) as described in How to check if you’re affected above. If validation fails, your config likely uses patterns accepted by v2 but not part of the v2.1 specification. Common issues are listed below. You can also consult the configuration reference for the full v2.1 schema.

HEREDOC syntax <<

Symptom: Error about unresolved pipeline expressions in run: steps.

Cause: Version 2.1 interprets << as a pipeline parameter expression. If your shell scripts use heredocs (e.g., cat <<EOF), the compiler will try to evaluate them.

Fix: Escape the << with a backslash:

# Before
- run:
    command: |
      cat <<EOF
      some content
      EOF

# After
- run:
    command: |
      cat \<<EOF
      some content
      EOF

Illegal characters in job names

Symptom: Error about invalid job name.

Cause: Version 2.1 requires job names to contain only letters, numbers, spaces, hyphens, and underscores. Characters like colons, parentheses, and commas are not allowed.

Fix: Rename any affected jobs:

# Before
jobs:
  build (linux):
    ...

# After
jobs:
  build-linux:
    ...

Unrecognized keys on job definitions

Symptom: Error about unexpected keys on a job.

Cause: Version 2.1 has a stricter schema and will reject keys that are not part of the job configuration reference.

Fix: Remove any keys from your job definitions that are not documented in the configuration reference. If a custom key was being used by external tooling that reads your config, consider moving that data to environment variables or a separate file.

branches: on job definitions

Symptom: Error about branches not being a valid key on a job.

Cause: Job-level branch filtering was replaced by workflow-level filters in v2.1.

Fix: Move branch filtering into your workflow configuration:

# Before (v2)
jobs:
  deploy:
    branches:
      only:
        - main

# After (v2.1)
workflows:
  build-and-deploy:
    jobs:
      - deploy:
          filters: pipeline.git.branch == "main"

Unsupported keys on Docker image definitions

Symptom: Error about unexpected keys on a Docker image definition.

Cause: Version 2.1 has a stricter schema for Docker image entries and will reject keys that are not part of the specification.

Fix: Remove any unsupported keys from your Docker image definitions. Refer to the Docker executor reference for the supported set of keys.

# Before
jobs:
  build:
    docker:
      - image: cimg/node:20.0
      - image: postgres:15
        environment:
          POSTGRES_DB: mydb
        ports:
          - "5432:5432"

# After
jobs:
  build:
    docker:
      - image: cimg/node:20.0
      - image: postgres:15
        environment:
          POSTGRES_DB: mydb

working-directory vs working_directory

Symptom: Job runs in the wrong directory or error about an unknown key.

Cause: The correct key is working_directory (with an underscore). The kebab-case form working-directory was silently ignored by v2, meaning it was never actually applied.

Fix: Use working_directory (underscore).

resource_class inside machine: executor

Symptom: Error about resource_class being in the wrong location.

Cause: In v2.1, resource_class must be a top-level key on the job, not nested inside the machine: block.

Fix:

# Before
jobs:
  build:
    machine:
      image: ubuntu-2404:current
      resource_class: large

# After
jobs:
  build:
    machine:
      image: ubuntu-2404:current
    resource_class: large

Pipeline parameter syntax

Symptom: Error about an undeclared pipeline parameter.

Cause: Expressions like << pipeline.parameters.x >> are treated as live expressions in v2.1 and must be declared. In v2, they were passed through as plain text.

Fix: Either declare the parameters in a parameters: block at the top of your config, or remove the expressions if they were not intended to be dynamic.

name: key on jobs

Symptom: Error about name not being a valid job key.

Cause: The name: key is not supported directly on job definitions in v2.1.

Fix: Remove the name: key from job definitions. To give a job a display name, set it in the workflow entry instead:

workflows:
  build:
    jobs:
      - my-job:
          name: "My Custom Name"

CIRCLE_COMPARE_URL not available in v2.1

Symptom: The environment variable CIRCLE_COMPARE_URL is empty or undefined.

Cause: CIRCLE_COMPARE_URL is only available in v2 configs and is not carried over to v2.1.

Fix: Use pipeline values to construct the compare URL yourself:

# Before (v2) — CIRCLE_COMPARE_URL was automatically available
- run: echo $CIRCLE_COMPARE_URL

# After (v2.1) — define it yourself using pipeline values
jobs:
  my-job:
    environment:
      CIRCLE_COMPARE_URL: << pipeline.project.git_url >>/compare/<< pipeline.git.base_revision >>..<<pipeline.git.revision>>
    steps:
      - run: echo $CIRCLE_COMPARE_URL


Need help?

If you’re unsure whether your config is affected or need help with a specific fix, reply to this thread or open a support ticket.

Update: Based on customer feedback, the breaking changes will now take effect on August 17th, 2026, rather than July 17th, 2026.

Hello, can CircleCI UI show a warning whenever the config validates with the upcoming changes? Installing random tools and IDE plugins is not really a user friendly approach. Regards.