Skip to content
Agentmart
Submit a tool

cleanup-deals

Find past-due, stale, incomplete, unexplained-loss, and likely test deals, then route each owner's flagged deals to them as one review task.

Skill by tomgranot

IndexedMIT★ 77
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/tomgranot-cleanup-deals

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Find past-due, stale, incomplete, unexplained-loss, and likely test deals, then route each owner's flagged deals to them as one review task. Never bulk-closes deals; deletes only test deals a person marked in a reviewed CSV. Skips Salesforce-synced deals.

Use cases: spreadsheets, testing, sales, productivity

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Python package

Network hosts it talks to 2

  • api.hubapi.com · found in code
  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/cleanup-deals

What's inside

Skill name
cleanup-deals
Description
Find past-due, stale, incomplete, unexplained-loss, and likely test deals, then route each owner's flagged deals to them as one review task. Never bulk-closes deals; deletes only test deals a person marked in a reviewed CSV. Skips Salesforce-synced deals.
License
MIT
Metadata
author: tomgranot · version: 1.2 · category: ongoing-maintenance
Instructions
146 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (5)

  • SKILL.md
  • after.py · scripts/
  • before.py · scripts/
  • deal_findings.py · scripts/
  • execute.py · scripts/

SKILL.md (first lines)

# Cleanup Deals

Standardize deal data to make pipeline reporting accurate. Test deals, missing amounts, and stale opportunities distort forecasts and pipeline metrics.

The fix for most bad deal data is a decision only the deal owner can make: is this deal alive, what is it worth, when will it close, why was it lost. So the scripted path here finds the problems precisely and puts them in front of the right person. It does not make those decisions for them.

## Prerequisites

- A HubSpot service key (`HUBSPOT_ACCESS_TOKEN` in `.env`; an existing legacy private app token also works) with:
  - `crm.objects.deals.read` and `crm.objects.owners.read` (all stages)
  - `crm.objects.contacts.write` (HubSpot gates task creation on it; review-task mode only)
  - `crm.objects.deals.write` (reviewed-deletion mode only)
- Python 3.10+ with [`uv`](https://github.com/astral-sh/uv)
- Knowledge of which deal pipelines are active and which are synced from Salesforce

## Important: Salesforce Sync Considerations

If deals are synced from Salesforce:
- Do NOT delete or modify synced deals without coordinating with the Salesforce admin.
- Changes in HubSpot may sync back to Salesforce and cause data loss.
- Identify synced deals by checking for the `hs_salesforceopportunityid` property.

Every script sets synced deals aside: they never appear in a finding CSV, never go into a review task, and `execute.py` refuses to delete them even if they are marked in a CSV. `before.py` reports how many were skipped per finding so you can raise them with the Salesforce admin.

## Scripts

Script paths are relative to this skill's directory. Run them from your project directory so `.env`, `data/`, and `reports/` resolve there: prefix each path with wherever this skill is installed (`skills/cleanup-deals/` in a clone of the repo).

| Stage | Script | Run with |
|-------|--------|----------|
| Before | [`scripts/before.py`](./scripts/before.py) | `uv run scripts/before.py [--stale-days 60] [--pipeline ID]` |
| Execute | [`scripts/execute.py`](./scripts/execute.py) | `uv run scripts/execute.py` (review tasks) |
| Execute | [`scripts/execute.py`](./scripts/execute.py) | `uv run scripts/execute.py --delete-from-csv PATH` (reviewed test-deal deletion) |
| After | [`scripts/after.py`](./scripts/after.py) | `uv run scripts/after.py` |

`before.py` is read-only: it exports one CSV per finding to `data/audit-logs/cleanup-deals-before-<finding>.csv` plus a summary the After stage compares against. `execute.py` creates one review task per deal owner, or deletes only the test deals a person marked. `after.py` re-counts every finding and prints a before/after table. All three share their finding definitions through [`scripts/deal_findings.py`](./scripts/deal_findings.py), so every stage counts the same deals. The scripts target HubSpot's date-based API version `2026-09` (`API_VERSION` in `deal_findings.py`).

### Findings

| Finding | Definition |
|---------|------------|
| `past-close-date` | Open deal whose `closedate` is before today |
| `stale` | Open deal with no logged activity (`notes_last_updated`) in N days (default 60). A deal with no activity at all counts only once it is older than N days. |
| `missing-amount-or-close-date` | Open deal with no `amount` or no `closedate` |
| `lost-no-reason` | Closed-lost deal with no `closed_lost_reason` |
| `likely-test` | Any deal whose name contains the word test, dummy, sample, or asdf, or with amount 0 and no associated contacts. "demo" is excluded on purpose: real deals are often named after the demo that opened them. |

## Why Execute Does Not Bulk-Close Deals

The obvious script would set every stale or past-due deal to Closed Lost. This skill does not ship one, for four reasons:

1. **Inactivity is not a loss.** `notes_last_updated` only moves when someone logs an activity in HubSpot. Deals worked by phone, from a personal inbox, or in another tool look stale while they are alive. A bulk close marks real pipeline as lost.
2. **A close is not cleanly reversible.** Moving a deal to Closed Lost writes a close date, fires stage-based workflows and notifications, and changes win rate, forecast, and quota attainment reports. Moving it back does not unsend the emails or undo the workflow actions, and the deal's stage history keeps the false loss.
3. **A bulk close destroys the signal it was meant to fix.** A loss reason filled in by a script ("Closed by cleanup") tells you nothing about why deals are lost, which is the reason to collect loss reasons at all.
4. **The owner has the answer.** Amount, close date, next step, and loss reason are facts the rep knows and the CRM does not. Routing each rep one task with their flagged deals gets those facts into the record. A script can only guess them.

Deletion is narrower still: only deals a person marked `yes` in the review column of the likely-test CSV, never Salesforce-synced deals, never closed-won deals (deleting them rewrites revenue history), and at most 100 per run.

## Step-by-Step Instructions

### Stage 1: Plan

Confirm with the user before starting:

1. Which pipelines are in scope, and which are Salesforce-synced (hands off without coordination)? Pass `--pipeline ID` to limit a run to one pipeline.
2. The staleness cutoff (default: 60 days without logged activity; `--stale-days N`).
3. Who receives the review tasks. Every flagged deal's owner gets one task. Deals with no owner or a deactivated owner are listed in the plan CSV but get no task: fix ownership first with `/cleanup-lead-owners` or `/reassign-deactivated-owners`.

### Stage 2: Before

```bash
uv run scripts/before.py
```

Present the counts table to the user, with the number of Salesforce-synced matches skipped. Walk through a few rows of each CSV to confirm the findings are right for this portal (for example, that the test-deal heuristic is not catching real deals).

**If the user wants test deals deleted**, have them open `data/audit-logs/cleanup-deals-before-likely-test.csv`, read the `reason` column, and put `yes` in the `delete` column of each row that is a genuine test deal. Leave every other row blank. Rows without `yes` are never deleted.

### Stage 3: Execute

**Review tasks (the default):**

```bash
uv run scripts/execute.py
```

The script groups the flagged deals by owner, writes the plan to `data/audit-logs/cleanup-deals-execute-tasks-plan.csv`, prints one line per owner, and asks for `CREATE TASKS`. Each owner gets one task, due in 7 days, associated with their flagged deals and listing why each was flagged. Owners who already have an open review task from a previous run are skipped, so re-running does not stack duplicate tasks on a rep. No deal is modified.

**Reviewed test-deal deletion (opt-in):**

```bash
uv run scripts/execute.py --delete-from-csv data/audit-logs/cleanup-deals-before-likely-test.csv
```

The script reads only the rows marked `yes`, re-reads those deals from HubSpot, refuses Salesforce-synced and closed-won deals, writes every deal's current properties to `data/audit-logs/cleanup-deals-execute-delete-backup.csv`, and asks for `DELETE`.

**Manual follow-ups the scripts do not cover:**

1. **Standardize pipeline stages**: ensure all pipelines have consistent stage names and probability percentages (Settings > Objects > Deals > Pipelines).
2. **Remove unused pipelines**: if a pipeline has zero active deals and is not in use, archive or delete it. The sales audit's Pipeline Configuration section lists them.

### Stage 4: After

```bash
uv run scripts/after.py
```

Prints Before, After, and Change per finding, using the stale cutoff and pipeline recorded by `before.py`. Review tasks change nothing on their own, so run this after the task due date: the counts fall as owners work their tasks. A finding that does not move points to a process gap (no required close date, no loss-reason field on the Closed Lost stage) rather than a data gap. See Tips.

## Safety Mechanisms

| Mechanism | Detail |
|-----------|--------|
| **Read-only Before and After** | `before.py` and `after.py` issue only searches and reads. The offline tests assert that neither makes a write call. |
| **No bulk close, no property edits** | `execute.py` has no code path that changes a deal's stage or properties. See "Why Execute Does Not Bulk-Close Deals". |
| **Abort thresholds** | Review tasks: 50 per run (`TASK_ABORT_THRESHOLD`), since each one lands on a person's task queue. Deletion: 100 deals per run (`DELETE_ABORT_THRESHOLD`), checked before any API call. Above either, the script exits without writing. Narrow with `--pipeline`, or raise the constant only after the user reviews the plan. |
| **CSV audit trail** | Task mode writes the full owner-by-deal plan before creating anything, then a log of created task IDs. Deletion mode writes every targeted deal's properties to a backup CSV before archiving, then a per-deal deletion log. |
| **Typed confirmation** | `CREATE TASKS` for review tasks, `DELETE` for deletion. Any other input aborts with nothing changed. |
| **Human review gate for deletion** | Only rows a person marked `yes` in the CSV's `delete` column are deleted. A CSV without that column is refused. |

From TomGranot/hubspot-admin-skills, MIT.

More from TomGranot/hubspot-admin-skills

Signals

Stars
77
Forks
18
Open issues
0
Last commit (this folder)
Oct 13, 2026 (15 days ago)

From GitHub (fetched Oct 24, 2026).

Details

Version
1.2
License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from tomgranot

Cite this page

cleanup-deals skill. Agent Mart. https://agentmart.dev/skills/tomgranot-cleanup-deals (last updated 2026-10-24)