nrwl / nrwl/nx

dependsOn silently drops any target whose name contains `[` (breaks @nx/jest Atomizer on Next.js dynamic routes)

Open
#36,498 0 comments 0 reactions 1 assignee View on GitHub

@AgentEnder is already working on this.

Since Aug 13, 2026.

priority: medium scope: core
Dominant language
TypeScript
Stars
29.4k
Forks
3k
Avg merge
2d 13h
Merged PRs (30d)
153

Description

Current Behavior

A dependsOn entry naming a target whose name contains [ is silently dropped from the task graph. The target exists, runs fine when invoked directly, and is listed in dependsOn — but it never runs as a dependency. No warning, exit code 0.

This is not a niche naming choice: the @nx/jest Atomizer generates one target per test file, so any project with Next.js App Router dynamic routes gets targets like test-ci--src/app/projects/[id]/page.test.tsx. Every one of those is dropped.

In our monorepo this meant 57 test files (~611 tests) never executed in CI while CI stayed green. They were only noticed because one of them had been failing locally for weeks against a green CI.

Expected Behavior

Either the exact target name resolves (preferred — the name is an exact match for an existing target), or Nx warns that a dependsOn pattern matched no targets. Silent omission of declared work is the harmful part.

Note there is already a warning for the analogous project case — Project patterns "..." does not match any projects. in processTasksForMultipleProjects. The target case has no equivalent.

Minimal Reproduction

packages/demo/project.json:

{
  "name": "demo",
  "targets": {
    "parent": {
      "executor": "nx:noop",
      "dependsOn": ["child--plain.test.ts", "child--src/app/[id]/page.test.ts"]
    },
    "child--plain.test.ts": {
      "executor": "nx:run-commands",
      "options": { "command": "echo RAN plain" }
    },
    "child--src/app/[id]/page.test.ts": {
      "executor": "nx:run-commands",
      "options": { "command": "echo RAN bracketed" }
    }
  }
}
$ nx run demo:parent

 NX   Running target parent for project demo and 1 task it depends on:
> echo RAN plain
RAN plain
 NX   Successfully ran target parent for project demo and 1 task it depends on

Two dependencies declared, one scheduled. RAN bracketed never prints. Exits 0.

Root Cause

expandWildcardTargetConfiguration in packages/nx/src/tasks-runner/utils.ts:

function expandWildcardTargetConfiguration(dependencyConfig, allTargetNames) {
    if (!isGlobPattern(dependencyConfig.target)) {
        return [dependencyConfig];
    }
    const matchingTargets = findMatchingTargets(dependencyConfig.target, allTargetNames);
    return matchingTargets.map((t) => ({ ...dependencyConfig, target: t }));
}

isGlobPattern (packages/nx/src/utils/globs.ts) returns true if the string contains any character in GLOB_CHARACTERS, which includes [:

export const GLOB_CHARACTERS = new Set(['*', '|', '{', '}', '(', ')', '[']);

So the name is routed to findMatchingTargets, which uses minimatch.filter(pattern). [id] is a character class matching a single i or d, so the pattern matches nothing — not even its own literal name:

const p = 'test-ci--src/app/projects/[id]/page.test.tsx';
minimatch(p, p);                       // false
minimatch(p.replace('[id]', 'i'), p);  // true

matchingTargets is empty, so the config expands to [] and the dependency disappears. The exact-name gate at projectHasTarget is never reached, and the target is present in the project graph throughout.

Possible Fix

Prefer an exact match before treating the name as a glob, e.g. in expandWildcardTargetConfiguration:

if (allTargetNames.includes(dependencyConfig.target)) {
  return [dependencyConfig];
}

That keeps wildcard dependsOn working while making a name that exactly identifies a real target always resolve. Failing that, warning when a target pattern matches zero targets would at least make it visible.

Environment

  • Nx 23.0.2. Verified 23.1.0 ships identical expandWildcardTargetConfiguration and GLOB_CHARACTERS, so upgrading does not help.
  • Reproduced on macOS, Node 24.16.0.
  • Surfaces via @nx/jest ciTargetName (Atomizer) + Next.js App Router dynamic route dirs, but the repro above needs neither plugin.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.