Problem
A 300K-contact CRM shared by four brands and 400+ users had built up years of duplicate properties, dead lists, junk tasks and reports nobody opened. A wound-down brand still had 38K records live. Cleaning that up by hand is impossible at this scale, and risky.
Constraints
Zero tolerance for touching live pipeline. Finance and sales still relied on some of the “junk.” Every action had to be reversible.
What I designed
A phased cleanup program in which every destructive action is a script with a dry run, a receipt of what it changed, and a rollback. Alongside it, The Governor: drift detection for users, roles, apps and schema-edit rights, with a Pipeline Controls view for finance.
How it works
Python and Node against the HubSpot API, about 12,000 lines for the property program alone. The Governor is a TypeScript monorepo of 10 packages and 78 tests, and its Pipeline Controls tab ships through n8n on a daily pull.
Result
Every number here is verified against the ledgers.
- 846 custom properties audited. 122 archived, and 8 merged with 101,847 values preserved.
- About 285,000 junk tasks archived, with a receipt for every run.
- 1,052 lists reviewed: 237 deleted and 304 renamed. 228 reports, 10 dashboards, 31 workflows and 21 forms removed.
- One brand retired: 38,412 records deleted and 24 assets removed, with zero impact on live deals.
- 43,345 records backfilled with their brand, with 0 failures.
- Before launch, caught a lead-status workflow value that would have made 17,658 qualified contacts demotable.