← All writing

Why your shallow merge just deleted 120 tenant logins — Unaib Amir

· 9 min read
auth0 identity migration scim postmortem

The TL;DR

One config field. A shallow merge instead of a deep one. Would have wiped login behaviour for 120 tenant connections and locked out 120,000 users.

Caught by a dry-run mode I almost cut from the script because “it adds complexity to a one-shot tool.”

The setup

[TODO — describe the fleet migration context]

The bug

// What I almost shipped:
const updatedConnection = { ...existingConnection, ...updatePayload };

// What it needed to be:
const updatedConnection = deepMerge(existingConnection, updatePayload);

[TODO — explain why this matters for Auth0 connections specifically]

The dry-run

[TODO — describe the diff output and the moment it surfaced]

What I changed

  • Swapped Object.assign style merges for explicit deep merges with a typed helper.
  • Made dry-run the default; production execution requires --confirm.
  • Added a snapshot of each connection’s full pre-change state to S3 before any update.

Generalising

[TODO — broader lesson about fleet operations]

FAQ

What broke?

An Auth0 connection update script used a shallow merge to combine the new payload with the existing connection config. The custom login scripts field was an object on the existing config but the update payload didn't carry it forward, so the merge would have written an empty object to all 120 tenant connections at once.

How was it caught?

A dry-run mode compared the resulting payload byte-for-byte against the existing config before sending it. The diff surfaced the login scripts being dropped.