Skip to content

cloudformation-diff: DependsOn changes are invisible in cdk diff for lists of two or more entries #1822

Description

@Adityaj0

Describe the bug

cdk diff reports no change for a resource whose DependsOn list was completely rewritten, as long as the number of entries stays the same and there are at least two of them.

dependsOnEqual is meant to compare two DependsOn arrays irrespective of element order. The loop that does it never runs past its first iteration:

cloudformation-diff/lib/diff/util.ts#L103-L117

if (Array.isArray(lvalue) && Array.isArray(rvalue)) {
  if (lvalue.length !== rvalue.length) {
    return false;
  }
  for (let i = 0 ; i < lvalue.length ; i++) {
    for (let j = 0 ; j < lvalue.length ; j++) {
      if ((!deepEqual(lvalue[i], rvalue[j])) && (j === lvalue.length - 1)) {
        return false;
      }
      break;                       // <-- unconditional, so j is only ever 0
    }
  }
  return true;
}

The break at the end of the inner body is unconditional, so the inner loop only ever executes j = 0. The single return false is guarded by j === lvalue.length - 1, which on that one iteration is only true when the array has exactly one element. For any array of length ≥ 2 the function therefore returns true no matter what the arrays contain — the length check is the only comparison that survives.

deepEqual routes every DependsOn key through this function, so the incorrect true propagates up to diffResource and the change is dropped from the diff.

Expected Behavior

Two DependsOn lists should compare equal when they contain the same entries, in any order, and unequal otherwise:

before after expected
['A', 'B'] ['B', 'A'] equal (reorder only)
['A', 'B'] ['C', 'D'] different
['A', 'B'] ['A', 'D'] different
['A', 'B', 'C'] ['B', 'C', 'D'] different

Current Behavior

Measured against main (536ad69), diffing two templates that are identical except for one resource's DependsOn:

before after differenceCount correct?
['A'] ['B'] 1
['A', 'B'] ['A'] 1 ✅ (length differs)
['A', 'B'] ['B', 'A'] 0 ✅ (reorder)
['A', 'B'] ['C', 'D'] 0 ❌ change is invisible
['A', 'B'] ['A', 'D'] 0 ❌ change is invisible
['A', 'B', 'C'] ['B', 'C', 'D'] 0 ❌ change is invisible

So exactly one shape of change is detected — a single-element list, or a change in list length. Every other real DependsOn change is silently dropped.

This is not a rare shape. CDK emits multi-element DependsOn routinely (a lambda.Function alone produces DependsOn: [FnServiceRoleDefaultPolicy…, FnServiceRole…], as noted in #1602), so any refactor that swaps one dependency for another while keeping the count the same shows up as an empty diff. The user sees "There were no differences" for a template that did change, and deployment ordering changes without ever being surfaced for review.

Reproduction Steps

import { diffTemplate } from '@aws-cdk/cloudformation-diff';

const template = (dependsOn: string[]) => ({
  Resources: {
    A: { Type: 'AWS::SNS::Topic' },
    B: { Type: 'AWS::SNS::Topic' },
    C: { Type: 'AWS::SNS::Topic' },
    D: { Type: 'AWS::SNS::Topic' },
    Consumer: { Type: 'AWS::SQS::Queue', DependsOn: dependsOn },
  },
});

// Consumer's dependencies are entirely replaced:
const diff = diffTemplate(template(['A', 'B']), template(['C', 'D']));
console.log(diff.resources.differenceCount);   // 0  <- expected 1

Possible Solution

Match each element on the left against a distinct element on the right, so duplicates are compared by multiplicity, and neither input array is reordered — the no-mutation property that #1575 / #1602 established:

if (Array.isArray(lvalue) && Array.isArray(rvalue)) {
  if (lvalue.length !== rvalue.length) {
    return false;
  }
  const unmatched = rvalue.map((_, i) => i);
  for (const l of lvalue) {
    const at = unmatched.findIndex((i) => deepEqual(l, rvalue[i]));
    if (at === -1) {
      return false;
    }
    unmatched.splice(at, 1);
  }
  return true;
}

All three existing DependsOn behaviours in template-and-changeset-diff-merger.test.ts are preserved: reordering stays equal, differing lengths stay unequal, and 'A' vs ['A'] stays equal (handled by the branch above this one).

On backwards compatibility: this makes cdk diff surface changes it currently hides. It does not invent differences — every newly reported change is a real edit to the template.

Additional Information/Context

The existing test suite covers reordering, differing lengths, and the string-vs-single-element-array case, but never two same-length arrays with different contents, which is why this survived.

I have a fix with regression tests for each row of the table above; all 151 tests in the package pass with it. The four new tests fail on main and pass with the fix.

CDK CLI Version

main (536ad69), reproduced against @aws-cdk/cloudformation-diff in this repo

Node.js Version

v24.1.0

OS

macOS 15 (Darwin 25.2.0)

Language

TypeScript

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions