Breaking Changes: Config Compilation Updates — July 17, 2026

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 July 17, 2026. Please refer to this post as the single source of truth going forward

On July 17, 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. Expand each section below to see what’s changing, how to check whether you’re affected, and how to resolve the issue.


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 check and fix your config

You can preview whether your config is affected today, before the change is 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 — look for undeclared-parameter errors in the output.
  3. Fix them — remove references to parameters not declared in the current scope, or declare the parameter where it’s used.
  4. Revalidate using the same UI toggle or CLI command to confirm the errors are resolved.

2. Regex engine update for when: matches: pattern:

The regex engine used to evaluate when: matches: pattern: conditions is being updated to improve the security and reliability of config compilation. After July 17, configs using any of the following regex features in pattern: fields will fail to compile:

  • Negative lookaheads
  • Negative lookbehinds
  • Possessive quantifiers
  • Backreferences

How to check if you’re affected

Search your .circleci/config.yml (and any orb source that feeds into your config) for when: matches: pattern: fields. If any pattern: value contains the features below, you’ll need to rewrite it.

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: "^(?!main).*$"
    value: << pipeline.git.branch >>

This matches any branch except main.

Fix:

branches:
  ignore:
    - main

Before (will break):

when:
  matches:
    pattern: "^(?!master|staging|release|hotfix).*$"
    value: << pipeline.git.branch >>

Fix: Replace with branches: ignore: or pipeline filter expressions (preferred, as expressions don’t couple your config to a specific VCS provider):

branches:
  ignore:
    - master
    - /^staging.*/
    - /^release.*/
    - /^hotfix.*/

Here’s a more complex example with multiple exclusions:

Before (will break):

^(?!develop$)(?!build_all$)(?!integration-....?)(?!integration2)(?!fedint-us1$)(?!hotfix/.*?/.*$).*

Fix:

branches:
  ignore:
    - develop
    - build_all
    - /^integration-.{4,5}/
    - /^integration2/
    - fedint-us1
    - /^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 package version strings like @myorg/some-pkg@1.2.3-rc.4 but excludes packages ending in -uat.

Fix: Split into and + not:

when:
  and:
    - matches:
        pattern: "^@myorg\\/[\\w-]+@\\d+\\.\\d+\\.\\d+-rc\\.\\d+$"
        value: << pipeline.git.tag >>
    - not:
        matches:
          pattern: "-uat@"
          value: << pipeline.git.tag >>

Or in pipeline logic, split into an only and an ignore rule that together cover the same space without a lookbehind.

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

when:
  matches:
    pattern: "^staging[0-9]?+$"
    value: << pipeline.git.branch >>

Fix:

when:
  matches:
    pattern: "^staging[0-9]?$"
    value: << pipeline.git.branch >>
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 — loosen the regex (recommended if tags are produced by release tooling):

when:
  matches:
    pattern: "^release/v\\d+\\.\\d+\\.\\d+/v\\d+\\.\\d+\\.\\d+$"
    value: << pipeline.git.tag >>

This no longer enforces that both version strings are identical, but in practice the tag is produced by tooling, so a mismatch is unlikely. Alternatively, if the set of valid values is small and known, enumerate them with a when: or: block.


3. End of support for version: 2.0

After July 17, 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 migrate

Step 1: Update the version key in your config:

# Before
version: 2.0

# After
version: 2.1

Step 2: Validate your config:

circleci config validate <path-to-config.yml>

If you don’t have the CLI installed, see the CLI installation docs. You can also use the in-app config editor, which will automatically validate and surface any errors.

Step 3: Fix any validation errors. If validation fails, your config likely uses patterns that were accepted by v2 but are 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. If your config includes extra keys on secondary containers, they will need to be removed.

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.