Flag HubSpot deals with missing fields and Slack the rep using a Claude Code skill
Install this skill
Download the skill archive and extract it into your .claude/skills/ directory.
missing-deal-fields.skill.zipThis skill works with any agent that supports the Claude Code skills standard, including Claude Code, Claude Cowork, OpenAI Codex, and Google Antigravity.
- One of the agents listed above
- HubSpot private app with
crm.objects.deals.readscope - Slack bot with
chat:writepermission, added to the target channel or able to DM users
Why a Claude Code skill?
The other approaches in this guide run on a fixed daily schedule. An Claude Code skill is different. You tell Claude what you want in plain language, and the skill gives it enough context to do it reliably.
That means you can say:
- "Check for deals with missing fields and DM the owners"
- "Which deals in the Proposal stage are missing close dates?"
- "Audit just deals owned by Sarah for missing amounts"
The skill contains workflow guidelines, API reference materials, and a Slack message template that the agent reads on demand. When you invoke the skill, Claude reads these files, writes a script on the fly, runs it, and reports results. If you ask for something different — a specific stage, a different set of required fields, or a channel post instead of DMs — the agent adapts without you touching any code.
How it works
The skill directory has three parts:
SKILL.md— workflow guidelines telling the agent what steps to follow, which env vars to use, and what pitfalls to avoidreferences/— HubSpot API patterns (deal search withNOT_HAS_PROPERTY, owners endpoint) so the agent calls the right APIstemplates/— a Slack Block Kit template so missing field alerts are consistently formatted across runs
When invoked, the agent reads SKILL.md, consults the reference and template files as needed, writes a Python script, executes it, and reports what it found. The reference files act as guardrails — the agent knows exactly which endpoints to hit, what filter operators to use, and how the responses are structured.
What is a Claude Code skill?
An Claude Code skill is a reusable command you add to your project that Claude Code can run on demand. Skills live in a .claude/skills/ directory and are defined by a SKILL.md file that tells the agent what the skill does, when to run it, and what tools it's allowed to use.
In this skill, the agent doesn't run a pre-written script. Instead, SKILL.md provides workflow guidelines and points to reference files — API documentation, message templates — that the agent reads to generate and execute code itself. This is the key difference from a traditional script: the agent can adapt its approach based on what you ask for while still using the right APIs and message formats.
Once installed, you can invoke a skill as a slash command (e.g., /missing-deal-fields), or the agent will use it automatically when you give it a task where the skill is relevant. Skills are portable — anyone who clones your repo gets the same commands.
Step 1: Create the skill directory
mkdir -p .claude/skills/missing-deal-fields/{templates,references}This creates the layout:
.claude/skills/missing-deal-fields/
├── SKILL.md # workflow guidelines + config
├── templates/
│ └── slack-missing-fields-alert.md # Block Kit template for Slack DMs
└── references/
└── hubspot-deals-search-api.md # HubSpot API patterns for deal field auditingStep 2: Write the SKILL.md
Create .claude/skills/missing-deal-fields/SKILL.md:
---
name: missing-deal-fields
description: Find HubSpot deals missing required fields (close date, amount) and DM each deal owner in Slack with a list of deals to fix
disable-model-invocation: true
allowed-tools: Bash, Read
---
## Goal
Search for active deals in HubSpot that are missing required fields, group them by owner, and send each owner a Slack DM listing their incomplete deals with direct links to fix them.
## Configuration
Read these environment variables:
- `HUBSPOT_ACCESS_TOKEN` — HubSpot private app token (required)
- `HUBSPOT_PORTAL_ID` — HubSpot portal ID for building deal links (required)
- `SLACK_BOT_TOKEN` — Slack bot token starting with xoxb- (required)
Default required fields: `closedate`, `amount`. The user may request additional fields.
Owner-to-Slack mapping: The agent needs a way to map HubSpot owner IDs to Slack user IDs. Check for an `owner_map.json` file in the skill directory first. If not found, fetch the Slack users list and HubSpot owners list and match by email address.
## Workflow
1. Validate that all required env vars are set. If any are missing, print which ones and exit.
2. For each required field, search HubSpot for active deals where that field has not been set. See `references/hubspot-deals-search-api.md` for the endpoint and filter syntax.
3. Deduplicate deals across searches (a deal missing both close date and amount appears in both results). Track which specific fields each deal is missing.
4. Group deals by `hubspot_owner_id`.
5. Resolve owner IDs to Slack user IDs using owner_map.json or by matching HubSpot owner emails to Slack user emails.
6. Send each owner a Slack DM using the format in `templates/slack-missing-fields-alert.md`.
7. Print a summary: total deals flagged, owners notified, and any owners without a Slack mapping.
## Important notes
- HubSpot's Search API applies AND logic within a filter group. You CANNOT combine multiple `NOT_HAS_PROPERTY` filters with OR in a single query. Run one search per field and merge results.
- `NOT_HAS_PROPERTY` only matches properties that were never set. If a field was set and then cleared, HubSpot may store it as an empty string. Consider also searching for `EQ` with empty string value.
- Exclude closed deals (`dealstage NOT_IN closedwon, closedlost`) from the search.
- Deal links follow the format: `https://app.hubspot.com/contacts/PORTAL_ID/deal/DEAL_ID`
- Slack user IDs start with U (not W). The bot must have chat:write scope to DM users.
- Use the `requests` library for HTTP calls and `slack_sdk` for Slack. Install them with pip if needed.Understanding the SKILL.md
Unlike a script-based skill, this SKILL.md doesn't contain a Run: command pointing to a script. Instead, it provides:
| Section | Purpose |
|---|---|
| Goal | Tells the agent what outcome to produce |
| Configuration | Which env vars to read and how to handle owner mapping |
| Workflow | Numbered steps with pointers to reference files |
| Important notes | Non-obvious context — especially the AND-only filter limitation |
The allowed-tools: Bash, Read setting lets the agent both read reference files and execute code. The agent writes its own script based on the workflow steps and reference materials.
Step 3: Add reference files
templates/slack-missing-fields-alert.md
Create .claude/skills/missing-deal-fields/templates/slack-missing-fields-alert.md:
# Slack Missing Fields Alert Template
Use this Block Kit structure when DMing a deal owner about missing fields.
## Block Kit JSON
```json
{
"channel": "<SLACK_USER_ID>",
"text": "Missing deal fields: <count> deals",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "🔍 Missing Deal Fields (<count> deals)"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<deal_lines>"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Please update these deals today so forecasting stays accurate."
}
]
}
]
}
```
## Deal line format
Each deal line: `- <https://app.hubspot.com/contacts/PORTAL_ID/deal/DEAL_ID|Deal Name> — missing *close date, amount*`
## Notes
- The top-level `text` field is required by Slack as a fallback for notifications.
- Bold the missing field names with asterisks (Slack mrkdwn).
- If a deal is missing multiple fields, join them with commas.
- Use singular "1 deal" when count is 1.
- Sort deals alphabetically by name within each owner's message.references/hubspot-deals-search-api.md
Create .claude/skills/missing-deal-fields/references/hubspot-deals-search-api.md:
# HubSpot Deals Search API Reference — Missing Fields
## Search for deals missing a property
Use `NOT_HAS_PROPERTY` to find deals where a field has never been set.
**Request:**
```
POST https://api.hubapi.com/crm/v3/objects/deals/search
Authorization: Bearer <HUBSPOT_ACCESS_TOKEN>
Content-Type: application/json
```
**Body (missing close date):**
```json
{
"filterGroups": [
{
"filters": [
{
"propertyName": "closedate",
"operator": "NOT_HAS_PROPERTY"
},
{
"propertyName": "dealstage",
"operator": "NOT_IN",
"values": ["closedwon", "closedlost"]
}
]
}
],
"properties": ["dealname", "amount", "closedate", "dealstage", "hubspot_owner_id"],
"limit": 100
}
```
**Body (missing amount):**
Same structure, but swap `closedate` for `amount` in the `NOT_HAS_PROPERTY` filter.
### Important: AND-only logic
HubSpot's Search API applies AND logic within a filter group. You CANNOT combine `closedate NOT_HAS_PROPERTY OR amount NOT_HAS_PROPERTY` in a single query. Run one search per field and merge results client-side.
### Catching cleared fields
`NOT_HAS_PROPERTY` only matches properties that were never set. If a rep entered a value and then cleared it, HubSpot may store an empty string. To catch both cases, add a second filter group:
```json
{
"filterGroups": [
{
"filters": [
{ "propertyName": "closedate", "operator": "NOT_HAS_PROPERTY" },
{ "propertyName": "dealstage", "operator": "NOT_IN", "values": ["closedwon", "closedlost"] }
]
},
{
"filters": [
{ "propertyName": "closedate", "operator": "EQ", "value": "" },
{ "propertyName": "dealstage", "operator": "NOT_IN", "values": ["closedwon", "closedlost"] }
]
}
],
"properties": ["dealname", "amount", "closedate", "dealstage", "hubspot_owner_id"],
"limit": 100
}
```
Multiple filter groups are combined with OR logic.
**Response shape:**
```json
{
"total": 8,
"results": [
{
"id": "12345",
"properties": {
"dealname": "Acme Corp Expansion",
"amount": null,
"closedate": null,
"dealstage": "qualifiedtobuy",
"hubspot_owner_id": "67890"
}
}
],
"paging": {
"next": { "after": "100" }
}
}
```
- `limit` max is 100. Use `paging.next.after` to paginate.
- Properties that have never been set return `null`.
## Get deal owners
Map HubSpot owner IDs to names and emails (for matching to Slack users).
**Request:**
```
GET https://api.hubapi.com/crm/v3/owners
Authorization: Bearer <HUBSPOT_ACCESS_TOKEN>
```
**Response shape:**
```json
{
"results": [
{
"id": "67890",
"email": "jane@company.com",
"firstName": "Jane",
"lastName": "Smith"
}
]
}
```
Use the owner's email to match against Slack users, or maintain a static `owner_map.json` mapping owner IDs to Slack user IDs.Step 4: Add the owner mapping (optional)
Create .claude/skills/missing-deal-fields/owner_map.json:
{
"12345678": "U0XXXXXXXXX",
"87654321": "U0YYYYYYYYY"
}Replace the keys with your HubSpot owner IDs and the values with the corresponding Slack user IDs. If you skip this file, the agent will attempt to match owners to Slack users by email address.
Step 5: Test the skill
Invoke the skill conversationally:
/missing-deal-fieldsClaude will read the SKILL.md, check the reference files, write a script, install any missing dependencies, run it, and report the results. A typical run looks like:
Searching for deals missing close date... found 6
Searching for deals missing amount... found 4
After deduplication: 8 deals with missing fields
Grouped by owner:
Jane Smith (3 deals): Acme Corp Expansion, Globex New Business, Initech Platform Deal
Bob Jones (2 deals): Umbrella Renewal, Stark Industries Upgrade
Unassigned (3 deals): skipped (no Slack mapping)
Sending Slack DMs...
Notified Jane Smith about 3 deals
Notified Bob Jones about 2 deals
Done. 8 deals flagged, 2 owners notified, 3 deals skipped (unassigned).What the Slack DM looks like
🔍 Missing Deal Fields (3 deals)
Acme Corp Expansion — missing close date, amount
Globex New Business — missing next step
Initech Platform Deal — missing amount
Please update these deals today so forecasting stays accurate.
Because the agent generates code on the fly, you can also make ad hoc requests:
- "Check just deals in the Proposal stage" — the agent adds a stage filter
- "Audit deals for close date, amount, and next step" — the agent adds another field
- "Post the audit results to #sales-pipeline instead of DMs" — the agent changes the Slack target
Make sure you have at least a few open deals in your pipeline with missing fields. If all deals are complete, the skill correctly reports "All active deals have complete fields" — that's not an error.
Step 6: Schedule it (optional)
Option A: Cron + Claude CLI
# Run every weekday at 7 AM
0 7 * * 1-5 cd /path/to/your/project && claude -p "Run /missing-deal-fields" --allowedTools 'Bash(*)' 'Read(*)'Option B: GitHub Actions + Claude
name: Daily Missing Deal Fields Audit
on:
schedule:
- cron: '0 12 * * 1-5' # 7 AM ET = 12 PM UTC, weekdays only
workflow_dispatch: {}
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: anthropics/claude-code-action@v1
with:
prompt: "Run /missing-deal-fields"
allowed_tools: "Bash(*),Read(*)"
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
HUBSPOT_ACCESS_TOKEN: ${{ secrets.HUBSPOT_ACCESS_TOKEN }}
HUBSPOT_PORTAL_ID: ${{ secrets.HUBSPOT_PORTAL_ID }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}Option C: Cowork Scheduled Tasks
Claude Desktop's Cowork supports built-in scheduled tasks. Open a Cowork session, type /schedule, and configure the cadence — hourly, daily, weekly, or weekdays only. Each scheduled run has full access to your connected tools, plugins, and MCP servers.
Scheduled tasks only run while your computer is awake and Claude Desktop is open. If a run is missed, Cowork executes it automatically when the app reopens. For always-on scheduling, use GitHub Actions (Option B) instead. Available on all paid plans (Pro, Max, Team, Enterprise).
0 12 * * 1-5 runs at 12 PM UTC (7 AM ET) on weekdays. GitHub Actions cron may also have up to 15 minutes of delay.
Troubleshooting
When to use this approach
- You want on-demand data audits — before pipeline reviews, after data imports, or during weekly cleanup
- You want conversational flexibility — different required fields, specific stages, or custom filters
- You want to iterate on what fields matter without editing scripts
- You're already using Claude Code and want deal hygiene as a quick command
- You want to run tasks in the background via Claude Cowork while focusing on other work
When to switch approaches
- You need reliable daily alerts with zero manual intervention → use n8n or the code approach
- You want a no-code setup with a visual builder → use Make
- You need alerts running 24/7 with zero cost and no LLM usage → use the Code + Cron approach
Common questions
Why not just use a script?
A script checks the same fields the same way every time. The Claude Code skill adapts — you can ask for different fields, filter by stage or owner, or post to a channel instead of DMs. The reference files ensure it calls the right APIs and uses the right filter operators, even when improvising.
Does this use Claude API credits?
Yes. The agent reads skill files and generates code each time. Typical cost is $0.01-0.05 per invocation. The HubSpot and Slack APIs themselves are free.
Can I check custom HubSpot properties?
Yes. Tell the agent which fields to check — it reads the reference file to understand the filter syntax and applies NOT_HAS_PROPERTY to whatever fields you specify. The SKILL.md defaults to close date and amount, but the agent adapts to any deal property.
How does the owner-to-Slack mapping work?
The agent first checks for an owner_map.json file in the skill directory. If found, it uses that. If not, it can match HubSpot owner emails to Slack user emails (requires users:read scope on the Slack bot). You can also just tell the agent to post everything to a shared channel instead.
Cost
- Claude API — $0.01-0.05 per invocation (the agent reads files and generates code)
- HubSpot API — included in all plans, no per-call cost
- Slack API — included in all plans, no per-call cost
- GitHub Actions (if scheduled) — free tier includes 2,000 minutes/month
Looking to scale your AI operations?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.