Cross-Stage RevOps RevOpsSales LeadershipMarketingFounder

CRM Data Cleanup on a Schedule: A Claude Agent That Proposes, You Approve

CRM data cleanup is the job every RevOps team owns and nobody schedules, so it happens as a panicked spreadsheet purge the week before the board deck. This play makes it a weekly run: a Claude agent reads the CRM over MCP, finds duplicates, non-standard values, empty fields, orphan records, and dead deals, proposes every fix as a line in a change list with the evidence beside it, and writes only the lines you tick. The database gets cleaner every week, and nothing merges without a human looking at it.

StageCross-Stage
Time to buildA day
DifficultyIntermediate
Best forRevOps, Sales Leadership, Marketing, Founder
THE RULEBOOK

The CRM data cleanup rulebook

The definitions the agent applies every week: what counts as a duplicate, what the canonical values are, which gaps earn a credit, when a deal is dead, and what it may never do without a human. Copy it, rewrite the canonical lists for your taxonomy, and save it as rules/crm-hygiene.md.

Match rules, canonical values, gaps, ownership, and write rules
Duplicate matching
  • companies: same domain (ignoring www and subdomains) = merge candidate; name similarity >= 0.90 plus same country = merge candidate
  • contacts: same email = merge; same full name at the same company = merge candidate
  • survivor: the record with the most activity, then the oldest; the import stub folds into the survivor
  • never: a cross-name merge on a shared parent domain (subsidiaries). Route to 'needs a human'
Canonical values (rewrite for your taxonomy)
  • job level: C-level, VP, Director, Manager, Individual contributor
  • industry: your 10-15 top-level categories, one spelling each; unmapped values go to 'needs a human'
  • country: ISO short names ('United States', 'United Kingdom'); state/region: full names
  • phone: E.164
Gaps worth a credit
  • contacts: title, company association
  • companies: industry, employee count, country
  • order: open deals, then customers, then ICP tier, then everyone else, within a fixed per-run budget
  • enrichment values are proposals; a human-entered value is never silently overwritten
Ownership and dead records
  • orphan: no owner, or an owner marked inactive; propose from territory rules, else the round-robin default
  • dead deal: open with no logged activity in > 90 days (tune to your cycle); propose closed-lost, reason 'No activity'
  • flag, never propose: deals above your review threshold, records modified in the last 48 hours
Write rules
  • the proposer never writes; the executor writes only ticked lines
  • every write is logged: record, field, before, after, timestamp
  • anything below the confidence floor goes to 'needs a human'

The subsidiary rule and the write rules are the two sections I would not soften. Everything else is taxonomy, and taxonomy is yours to set.

The stack

The stack

How the tools connect
What a run costs
Per week
one audit over a few thousand records, a couple of dollars on a mid-tier model
Enrichment
the only real variable: set a credit budget per run and order by account value
CRM + Slack
free over MCP, no data-quality SaaS seat
Review time
an hour in week one, 15 minutes by week four
The problem

The problem

Every CRM decays at the same rate its team works. A rep types 'VP Sales' and the form already had 'Vice President, Sales' and 'VP of Sales' and 'vp sales'. Marketing imports a webinar list and 400 companies arrive with no industry, no size, and three spellings of the same country. Someone leaves and 1,200 records point at an owner who no longer exists. Nobody did anything wrong. The data simply has more ways to be entered than to be maintained.

The cost shows up downstream, where it is hard to trace back. The territory report is off because 'United States', 'USA', and 'US' are three segments. Lead routing sends an existing customer to a new-business rep because the company exists twice. The board deck's win rate is wrong because 90 deals from 2024 are still open at 'Proposal'. Each of these is a data problem that presents as a strategy problem, and the strategy conversation happens first.

The usual fix is a cleanup project: export to a spreadsheet, dedupe with fuzzy match, normalize with find-and-replace, import, hold your breath. It works once, takes a week, and the decay restarts the day it finishes. Worse, bulk tools are confident. A fuzzy match that merges two records with the same domain root will happily fold two real companies into one, and you find out when a customer asks why their account is named after someone else.

A Claude agent changes the shape of the work. It runs weekly, reads the whole database over MCP, and applies a rulebook you wrote: what counts as a duplicate, what the canonical values are, which gaps are worth an enrichment credit, when a deal is dead. It writes every proposed change as a line with its evidence, a match score, the two records side by side, the field before and after. You review the list, tick the lines you agree with, and a separate executor writes only those, logging each change. Cleanup becomes a fifteen-minute review instead of a week-long project, and the merge that needs a human gets one.

How it works

How it works

The workflow, end to end
  1. 01 Run Sunday night Claude Code (scheduled)reads the rulebook first
  2. 02 Read the CRM HubSpot (MCP)companies, contacts, deals, owners
  3. 03 Find candidates Claudeduplicates, values, gaps, orphans, dead deals
  4. 04 Fill gaps FullEnrich / Claywithin a credit budget
  5. 05 Propose Checklist file + Slackevidence + confidence, all unticked
  6. 06 Tick to approve You15 minutes by week four
  7. 07 Execute + log Executor (MCP writes)ticked lines only, before/after log
  • A scheduled Claude Code job runs weekly and reads contacts, companies, deals, and owners over the CRM's MCP connector
  • It applies your hygiene rulebook: duplicate matching rules, canonical value lists, required fields, ownership rules, dead-deal thresholds
  • It finds candidates in five buckets: duplicates, non-standard values, gaps, orphans (no owner or an inactive owner), and dead deals
  • It fills the gaps it can from an enrichment source, within a per-run credit budget you set
  • It writes a proposal list: one line per change, with the evidence, a confidence score, and an unticked checkbox, and posts it to Slack
  • You tick the lines you approve; a separate executor writes only those to the CRM and posts a before-and-after change log
See it run

See it run

zsh
$# Sunday night: the proposer runs read-only and writes the checklist
$claude -p "$(cat briefs/crm-hygiene.md)" --allowedTools mcp__hubspot__search_companies,mcp__hubspot__search_contacts,mcp__hubspot__search_deals,mcp__hubspot__get_owners,mcp__fullenrich__enrich,mcp__slack__post_message,Read,Write
Read 4,812 companies · 19,340 contacts · 611 open deals · 23 owners (3 inactive). Skipped 96 records modified < 48h
Duplicates 41 (shared domain 29, name match 12) · Non-standard values 318 (industry 204, country 71, job level 43) · Gaps 1,140 (filled 200 within budget) · Orphans 412 · Dead deals 57 (4 flagged > $50k)
Needs a human: 9 (7 shared-parent-domain merges, 2 unmapped industries)
Wrote proposals/2026-09-06.md (618 lines, 0 ticked). Posted counts to #revops
$# Monday, after review: the executor writes only the ticked lines
$claude -p "$(cat briefs/crm-hygiene-execute.md)" --allowedTools Read,Write,mcp__hubspot__update_company,mcp__hubspot__update_contact,mcp__hubspot__merge_companies,mcp__hubspot__update_deal,mcp__slack__post_message
Applied 561 of 618 (57 unticked skipped). Merged 34 · normalized 301 · filled 194 · reassigned 412 · closed-lost 47. Log: changes/2026-09-07.csv
$
The playbook

The playbook

Write the rulebook before you touch a record

The agent is only as good as the definitions you give it, so start with the arguments RevOps has been having in Slack for years and settle them in a file. What makes two companies the same: same domain, or same domain plus a name similarity above a threshold, and what about subsidiaries with a shared parent domain? What are the canonical values for job level, industry, country, and state? Which fields are required on a contact before it can be routed, and which are nice to have? When is an open deal dead: no activity in 90 days, or 120, and does the rule change by stage?

Write it as a spec the agent reads at the start of every run, and keep it in version control next to the brief. The rulebook below is the one I start from; the canonical lists are the part every team rewrites, because your industry taxonomy and your seniority ladder are yours.

Decide the confidence tiers now. A merge candidate with the same domain and a 0.95 name match is a different proposal than one with a similar name and no domain. The list should show the score and the evidence for every line, so the reviewer can approve the obvious ones in a second and spend their attention on the ones that deserve it.

💡

TipPut the subsidiary rule in writing on day one. Shared parent domains are how two real companies become one record, and the rule that prevents it is a sentence: 'Do not propose a merge across different company names on a shared parent domain; flag it for a human instead.'

Connect the CRM over MCP and run the audit read-only

Connect HubSpot or Salesforce to Claude Code through its MCP connector, using an account whose permissions match the job. For the first several runs, only allow the read tools. The agent should be able to search and fetch contacts, companies, deals, and owners, and it should not yet be able to create, update, merge, or delete anything.

Run the audit once by hand and read the counts. How many companies share a domain? How many distinct values does the industry field hold, and how many of them are spelling variants? How many records point at deactivated owners? How many open deals have had no activity in 90 days? Those numbers are the baseline you will show the CRO in a quarter, and they are usually worse than anyone guessed.

Name your custom properties and pipeline stages explicitly in the brief. An agent that assumes the standard HubSpot fields will miss the 'Account Tier' property your routing depends on, and will treat your custom 'Legal Review' stage as unknown. Ten minutes listing the fields saves a run's worth of wrong proposals.

  • Reads: companies (name, domain, industry, size, country, state, owner, created, last modified)
  • Reads: contacts (name, email, title, company association, owner, lifecycle stage, last activity)
  • Reads: deals (name, stage, amount, close date, owner, last activity, stage history)
  • Reads: owners and their active status
  • Never writes, in the audit phase: anything

Write the brief so every proposal carries its evidence

The brief turns the rulebook into a run. Read everything, apply the rules, sort candidates into the five buckets, and for each candidate write one line: what changes, on which record, from what to what, why, and how confident. The 'why' is the two records side by side for a merge, the raw value and the canonical value for a normalization, the enrichment source for a fill, the last-activity date for a dead deal. A proposal without evidence is an opinion, and opinions are what the spreadsheet purge already had.

Tell it what to leave alone. Records modified in the last 48 hours are in use; skip them. Deals with an amount above a threshold get flagged, never auto-proposed as dead. Anything the subsidiary rule catches goes to a 'needs a human' section instead of the merge list. The brief should make the agent conservative by default, because a conservative proposal list is one the reviewer can trust enough to approve quickly.

Fix the output as a checklist. Every line starts with an unticked box. The executor, later, treats an unticked line as a no-op. That single convention is the safety model for the whole play: the agent can propose a thousand changes and write zero of them until a person ticks.

The weekly CRM hygiene brief
Every Sunday at 9:00pm {{TIMEZONE}}, run the CRM hygiene audit against {{CRM}} using the rulebook in rules/crm-hygiene.md.

Read all companies, contacts, deals, and owners via the {{CRM}} connector. Skip any record modified in the last 48 hours.

Find candidates in five buckets:
1. DUPLICATES: companies sharing a domain, or name similarity >= {{NAME_MATCH}} with matching country; contacts sharing an email, or same name at the same company. Never propose a merge across different company names on a shared parent domain; put those under NEEDS A HUMAN.
2. NON-STANDARD VALUES: any industry, job level, country, or state value not in the canonical lists. Map to the canonical value; if no confident mapping exists, list it under NEEDS A HUMAN.
3. GAPS: contacts missing title or company; companies missing industry, size, or country. Fill from {{ENRICHMENT_SOURCE}} up to a budget of {{CREDIT_BUDGET}} credits per run, highest-value accounts first (open deals, then customers, then ICP tier).
4. ORPHANS: records with no owner or an inactive owner. Propose the owner from the territory rules; if none applies, propose the round-robin default.
5. DEAD DEALS: open deals with no logged activity in > {{DEAD_DAYS}} days. Propose closed-lost with reason 'No activity'. Deals above {{FLAG_AMOUNT}} are flagged, never proposed.

Write proposals/{{DATE}}.md as a checklist. One line per change:
- [ ] <bucket> | <record id> | <field> | <before> -> <after> | evidence: <the two records, the source, or the last-activity date> | confidence: <0-1>

Rules:
- Read only. Do not create, update, merge, or delete anything in the CRM.
- Every line must cite evidence a reviewer can check in one click.
- Sort each bucket by confidence, highest first. Put anything below {{MIN_CONFIDENCE}} under NEEDS A HUMAN.
- End with the counts per bucket and the change vs last week's audit.
- Post the counts and a link to the file in #{{REVOPS_CHANNEL}}.
💡

TipMake 'needs a human' a first-class section, and make it the one you read first. The merges the agent is unsure about are exactly the ones that used to go wrong in the bulk tool.

Fill the gaps within a budget, not to completion

Enrichment is where hygiene projects quietly burn money. Run the gap-fill through FullEnrich or Clay with a hard per-run credit budget and an ordering rule: accounts with open deals first, then current customers, then anything in your ICP tier, then everyone else. A contact from a 2023 webinar with no title is a gap; it is also not worth a credit this week, and maybe not ever.

Treat enrichment results as proposals too. The source says the company has 620 employees and the CRM says 40; that is a line in the list with both values, not a silent overwrite. Enrichment vendors are usually right and occasionally spectacularly wrong, and the record you most want to protect is the one where a human typed the real number after a call.

Log the credits spent and the fill rate each run. If a third of the credits go to records nobody ever touches again, the ordering rule needs tightening, and that is a one-line edit in the brief.

Review, tick, and let a separate executor write

The proposal list lands in Slack Monday morning as a file link and the bucket counts. The owner opens it and reads the way a good editor reads: approve the high-confidence normalizations in bulk, scan the merges with the two records side by side, and spend real attention on 'needs a human'. Ticking a line is the approval. A first review of a neglected database takes an hour; by week four it is fifteen minutes, because the backlog is gone and the run only finds the week's decay.

The executor is a second, smaller job with write permissions, and it does one thing: read the ticked lines and apply them, one at a time, logging the record, the field, the before, the after, and the timestamp. It never reads the rulebook, never infers, never writes an unticked line. Separating the proposer from the executor is what lets you give write access to the CRM without giving it to the agent that has opinions.

Post the change log back to the channel. 'Merged 14 companies, normalized 212 industries, filled 61 titles, reassigned 38 orphans, closed 27 dead deals' is a sentence the sales leader reads and trusts, and the log underneath is the audit trail when someone asks in March why a record changed in January.

  • Proposer: read-only CRM tools + Write for the proposal file
  • Reviewer: a human, ticking boxes in the file or a Slack thread
  • Executor: CRM write tools, reads only the ticked lines, logs every change
  • Rollback: the change log is the undo list; keep it
💡

TipGive the executor no access to the rulebook or the enrichment source. It should be boring on purpose: ticked line in, one write out, one log line. The intelligence all lives in the proposer, which cannot write.

Schedule it and watch the counts fall

Run the proposer Sunday night as a scheduled Claude Code job so the list is waiting Monday, and run the executor whenever the review is done, by hand at first, then on a trigger once you trust the pattern. Keep the rulebook, the brief, and the proposal files in a repo; the history of what was proposed and what was approved is the most honest record of your data quality you will ever have.

Track five numbers weekly: duplicate candidates, non-standard values, gap count, orphans, dead deals. Week one is the backlog and the number is embarrassing. By week six the run should find only the week's decay, a few dozen lines, and the review takes a coffee. That trend line is the report for the CRO, and it makes the case for the next play, because routing, scoring, and forecasting all get better on clean data.

Revisit the rulebook quarterly. New canonical values appear when the company enters a new market; a new pipeline stage changes the dead-deal rule; a reorg changes the territory rules. The agent applies the rules exactly, which means the rules have to be right, and the quarterly read is where you keep them right.

Inside the prompt

Inside the prompt

The scoring prompt is short, but every line is there for a reason. Here is what each one is doing and why.

Why each line is in the hygiene brief
Rulebook first"using the rulebook in rules/crm-hygiene.md"
The definitions live in a file you version, argue about once, and revisit quarterly. The agent applies them; it does not invent them.
Skip recent records"Skip any record modified in the last 48 hours"
In-use records are mid-conversation. Cleaning them is how a rep loses a note.
The subsidiary rule"Never propose a merge across different company names on a shared parent domain"
The one sentence that would have saved my worst cleanup. Cross-name matches go to a human.
Evidence on every line"evidence: the two records, the source, or the last-activity date"
A proposal a reviewer can check in one click gets approved in one second. One without evidence gets ignored.
Unticked by default"- [ ] | | ..."
The checkbox is the approval. The executor treats an unticked line as nothing to do.
Read only"Do not create, update, merge, or delete anything in the CRM"
The agent with judgment cannot write. The agent that writes has no judgment. That split is the safety model.
What you get

What you get

The Monday proposal list, one bucket shown. Every line carries the change, the evidence, and a confidence score; every box starts unticked. The executor writes only what gets ticked.

Example output
CRM HYGIENE · proposals/2026-09-06.md · 618 proposals · 0 ticked

DUPLICATES (41)
- [ ] merge | company 88213 -> 71902 | "Meridian Freight Inc" + "Meridian Freight" | evidence: same domain meridianfreight.com, same city, 71902 has the open deal ($110k) and 6 contacts; 88213 has 1 contact, created by import 2026-08-12 | confidence: 0.97
- [ ] merge | contact 40311 -> 40298 | d.ruiz@meridianfreight.com + dana.ruiz@meridianfreight.com | evidence: same name, same company, 40298 has the call history | confidence: 0.93
- [ ] merge | company 91004 -> 66120 | "Halden Payments" + "Halden Payments Ltd" | evidence: name match 0.91, domains halden.com vs halden.co.uk, both London | confidence: 0.71

NON-STANDARD VALUES (318)
- [ ] normalize | company 70215 | industry | "Software - SaaS" -> "Software" | evidence: canonical list v3 | confidence: 0.99
- [ ] normalize | contact 51207 | job_level | "Vice President, Sales" -> "VP" | evidence: title contains 'Vice President' | confidence: 0.98
- [ ] normalize | company 70990 | country | "U.S.A." -> "United States" | evidence: canonical list v3 | confidence: 0.99

DEAD DEALS (57, 4 flagged)
- [ ] close-lost | deal 30871 | "Corvid Analytics - Expansion" | stage Proposal, $18k, last activity 2026-04-02 (157 days), owner inactive | reason: No activity | confidence: 0.95
- FLAG (not proposed) | deal 30422 | "Northwind - Platform" | $180k, last activity 96 days, owner Marcus | above $50k threshold, review with owner

NEEDS A HUMAN (9)
- ? | company 77120 + 77121 | "Tessaro Health" + "Tessaro Health Partners" | shared parent domain tessaro.com, different names, different states | rule: no cross-name merge on shared parent domain
- ? | industry value "Fintech Infrastructure" (14 companies) | no canonical mapping; add to list or map to "Financial Services"?

COUNTS vs last week: duplicates 41 (-12) · non-standard 318 (-140) · gaps 1,140 (-260) · orphans 412 (new: 3 owners deactivated Thu) · dead 57 (-9)
Anatomy of one proposed merge
company 88213 -> 71902 · "Meridian Freight Inc" + "Meridian Freight" · confidence 0.97 · unticked
match basissame domain, same city
The rulebook says domain plus a second signal. Name similarity alone never reaches the merge bucket.
survivor71902 (the open deal and 6 contacts)
The record with the history survives. The import stub from August folds into it, never the other way round.
evidenceboth records side by side, created-by and created-on
The reviewer sees what the matcher saw. An unexplained score is an opinion; two records are a decision.
confidence0.97
Sorted to the top of the bucket so the reviewer approves the obvious ones fast and saves attention for the 0.71s.
checkbox[ ]
Unticked means no-op to the executor. The agent can propose a thousand merges and perform none of them.
after executionone line in changes/2026-09-07.csv
Record, field, before, after, timestamp. The log is the undo list when someone asks in March.
Pitfalls to avoid

Pitfalls to avoid

⚠️

Letting the proposer writeThe whole safety model is that the agent with opinions cannot touch the CRM. Keep the proposer read-only and give write tools only to a separate executor that reads ticked lines and nothing else.

⚠️

Fuzzy-matching across a shared parent domainTwo real companies on subsidiaries of one domain look like a duplicate to every matcher. Write the rule that routes cross-name matches on a shared parent domain to 'needs a human', and read that section first.

⚠️

Enriching to completionA database always has more gaps than credits. Set a per-run budget and an ordering rule (open deals, customers, ICP tier, then everyone) and let the long tail stay empty.

⚠️

Overwriting human-entered valuesThe enrichment source says 620 employees, the rep typed 40 after a call. Present both as a proposal line; never let a vendor field silently win over a field a person set.

⚠️

Cleaning records that are in useA record modified in the last 48 hours is mid-conversation. Skip it this week. Merging a company while a rep is updating the deal on it is how you lose a note.

⚠️

A rulebook nobody re-readsThe agent applies the rules exactly, including the stale ones. Revisit canonical lists and dead-deal thresholds quarterly, when the market map, the pipeline, or the territories change.

FAQ

Questions people ask

What does CRM data cleanup actually involve?
Five jobs, on repeat: merging duplicate companies and contacts, normalizing free-text fields (industry, job level, country, state) to a canonical list, filling the gaps that matter (title, company size, industry) from an enrichment source, reassigning records whose owner left or was never set, and closing deals that have been open with no activity for months. This play runs all five weekly and hands you the proposals to approve.
Why propose changes instead of just making them?
Because the changes that go wrong are the merges, and a wrong merge is expensive to unwind. A proposal list with the evidence beside each line lets you approve 90% in bulk and spend real attention on the 10% that need it. It also gives you a log of every change, which is the audit trail when someone asks why a record changed.
Can it write to the CRM at all?
Yes, through a separate executor that has write permissions and reads only the ticked lines from the proposal list. The agent that finds problems is read-only. The agent that writes does not read the rulebook or make judgments. Keeping them separate is what makes it safe to give write access.
How does it avoid merging two different companies?
The rulebook requires a domain match plus a second signal for a merge proposal, and it routes any cross-name match on a shared parent domain to a 'needs a human' section instead of the merge list. Subsidiaries are the case that fools every fuzzy matcher, so they are named in the rules.
How much does enrichment cost?
As much as you let it. Set a per-run credit budget and an ordering rule (accounts with open deals, then customers, then ICP tier, then the rest). A contact from a 2023 webinar with no title is a gap and also not worth a credit. Log the fill rate and tighten the ordering when credits go to records nobody touches.
How long until the database is clean?
Week one is the backlog: a long list and an hour of review. By week four to six the run finds only the week's decay, a few dozen lines, and the review is a coffee. The five bucket counts, tracked weekly, are the trend line to show the CRO.
HubSpot or Salesforce?
Either. Both have MCP connectors that expose the read and write operations the play needs. Name your custom properties and pipeline stages in the brief, because the agent will assume the standard fields otherwise and miss your setup.
Related

Related plays

Want playbooks like this in your inbox?

A new AI use case, prompt, or teardown every couple of weeks.

Subscribe →