Building a Sales Objection Library with Claude Code, Step by Step
Date: July 6, 2026 • Author: Second Axis
Your reps hear the same fifteen objections over and over, and every one of them answers differently. The best rep has a devastating response to "you're more expensive than X" that she worked out in March; the newest rep is improvising a worse one on a live call right now. Somewhere there's a battlecard doc, last edited two quarters ago, that nobody opens because half of it is wrong and nobody knows which half. Meanwhile your competitors' unhappy customers are writing detailed public complaints on G2, Reddit, and Trustpilot, which is a free map of exactly where the alternatives are weak, and nobody on your team has read it end to end.
Can you build the real thing with Claude Code: a library that mines both sources, keeps itself current, and that sales actually trusts? Yes, and this playbook is the build. The text-mining machinery (extraction schemas, embedding clusters with stable IDs, agents reading summaries instead of raw text) is the same machine as our conversation intelligence playbook, and this post will not re-explain it. What's specific here is the part that makes the output trustworthy: versioning with approval. Every response has an owner, an approval state, and a change history. Agents draft; only humans approve; agents never touch an approved response except to propose a change. That governance layer is the difference between a library reps quote on calls and a doc that dies in a drive folder.
What you're building
- A canonical objection set: the 20-40 objections that actually occur, each with a stable ID, distilled from what prospects said verbatim on your calls and what reviewers say about your competitors in public.
- Per objection, a response file: the recommended answer, grounded in two kinds of evidence: your approved positioning docs, and the real answers your winning reps gave in deals that closed. Never an invented product claim.
- An approval workflow: drafts arrive as pull requests, a named approver from sales enablement signs off, and the approved response carries
approved_byand a date in its frontmatter. Git history is the change log. - Staleness triggers: when a competitor changes pricing or ships a feature, the affected responses get flagged for re-review instead of quietly rotting.
- Delivery reps will actually use: a searchable markdown library in git as the source of truth, a published web page for browsing, and optionally a Slack lookup.
The scale math
This is the small end of text-at-scale, and it's worth saying so. Assume 30 sales calls a week at roughly 8-10K tokens of transcript each: about 300K tokens per week of new call data. A one-time backfill of the last two quarters is maybe 350-700 calls, call it 3-6 million tokens. The competitor review corpus might be 1,000-3,000 reviews at 100-300 tokens each: another 0.5 million tokens or so up front, with a trickle afterward.
That's two orders of magnitude below the conversation-intelligence workload, but the architecture conclusion is the same: the backfill alone is 15-30x a 200K context window, so nothing reads the raw corpus directly. A cheap-model extraction pass turns each transcript and review into a small structured record; embeddings and clustering group the records into canonical objections; Claude's agents read only cluster evidence packs. The anchor playbook covers that three-layer design and the stable-cluster-identity problem in detail. Here, the weekly increment is small enough that extraction cost is close to a rounding error; the hard part of this system is governance, not volume.
Phase 0: Prerequisites
- Claude Code installed and authenticated, Pro/Max/Team/Enterprise if you want the scheduling and artifact-publishing steps.
- An API key for the extraction script. Same pattern as the anchor playbook: extraction runs as a script against the Batch API (about 50% discounted), which is metered spend separate from your subscription. At this volume it's small; numbers at the end.
- Access to call transcripts. If you're on Gong or Fireflies, there are community MCP servers (kenazk/gong-mcp, Props-Labs/fireflies-mcp); both are community projects, not vendor-official, so vet them before pointing them at customer calls. For bulk export, both vendors' APIs via your own script is the cleaner path anyway: MCP for drill-downs, scripts for the pipeline.
- A review-collection decision, made up front. G2, Trustpilot, and Reddit each have terms of service, and scraping may violate them. Prefer sanctioned paths: official APIs and data programs where they exist, manual export where they don't, and accept a smaller corpus if the sanctioned path is narrow. If your competitors are mobile apps, the community AppInsightMCP covers app-store reviews. Whatever you choose, record source and retrieval date on every review record; evidence you can't trace is evidence you can't use.
- Your positioning docs, committed to the repo. This is non-negotiable and specific to this workflow: the drafter and critic need a source of truth for product claims, and "somewhere in Google Docs" is not checkable by an agent. Export the approved messaging doc, pricing one-pager, and competitive claims sheet into a
positioning/folder in the repo, and keep them current. - A named approver. A human in sales enablement or PMM who owns sign-off. If you can't name this person, stop; the whole design assumes them.
- Python 3.11+ with the usual stack (
duckdb,pandas,scikit-learn, an embeddings model), and a dedicated git repo.
Phase 1: The repo, with the library as the versioned asset
objection-library/
├── CLAUDE.md
├── .claude/
│ ├── agents/
│ │ ├── extractor.md # QA on extraction batches
│ │ ├── drafter.md # drafts/revises one response
│ │ └── critic.md # product-claim verification
│ └── skills/
│ └── objection-refresh/
│ └── SKILL.md
├── positioning/ # source of truth for product claims
├── scripts/
│ ├── export_calls.py # transcripts since watermark → data/
│ ├── fetch_reviews.py # sanctioned review collection → data/
│ ├── extract.py # Batch API map pass → records
│ └── cluster.py # assign records to canonical objections
├── state/
│ ├── objections.json # canonical registry: stable IDs, centroids
│ └── watermark.json
├── data/ # gitignored: transcripts, reviews, records
├── library/ # THE ASSET
│ ├── pricing/
│ │ └── OBJ-014-too-expensive-vs-competitor-x.md
│ ├── security/
│ ├── missing-features/
│ └── switching-costs/
└── runs/ # committed: weekly run outputs
Everything except library/ follows the anchor playbook's shape. library/ is the new idea: one markdown file per canonical objection, and the frontmatter is the governance system:
---
id: OBJ-014
objection: "You're 40% more expensive than Competitor X"
category: pricing
competitors: [competitor-x]
status: approved # draft | in_review | approved | stale
owner: jane@yourco.com
approved_by: jane@yourco.com
approved_date: 2026-06-22
evidence_calls: 23 # times heard, trailing 90 days
last_evidence: 2026-07-02
sources:
- positioning/pricing-onepager.md
- "call 8841 (won): rep reframe to total cost"
- "G2 review of Competitor X, 2026-05, hidden overage fees"
---
## The objection
[verbatim examples, deal stages where it appears]
## The response
[the approved answer]
## Why this works
[evidence: win-rate context, the reviews that back each point]
The state machine is simple and strict: agents create files as draft, a PR moves them to in_review, and only a human merge with the approver's sign-off makes them approved. Write the enforcement into CLAUDE.md so every session, including scheduled ones, inherits it:
## Library rules (never violate)
- NEVER edit a file with status: approved. To change one, copy it
to a draft with a `supersedes:` field and open a PR.
- Every product claim in a response must be traceable to a file
in positioning/ or a verbatim rep quote from a won deal.
- status: approved requires approved_by and approved_date, set by
the human approver at merge, never by an agent.
Phase 2: Ingestion and extraction
export_calls.py pulls new transcripts since state/watermark.json; fetch_reviews.py collects reviews through whatever sanctioned path you chose in Phase 0. Then extract.py runs the map pass over both, with the Batch API and the cheapest model that survives your spot-checks (hand-grade 50 extractions before trusting it). One schema, two source types:
{
"source_type": "call",
"source_id": "call-8841",
"objection_verbatim": "honestly the pricing just doesn't work for a team our size",
"category": "pricing",
"competitor_named": "competitor-x",
"deal_stage": "negotiation",
"rep_response_summary": "reframed to per-seat total cost incl. overages",
"rep_response_verbatim": "...",
"deal_outcome": "won",
"schema_version": 2
}
Review records use the same shape with deal_stage and rep fields null, and objection_verbatim holding the reviewer's complaint about the competitor. Two fields do disproportionate work later. deal_outcome (joined from your CRM at export time) lets the drafter find answers that correlate with deals surviving instead of answers that merely sound good. And rep_response_verbatim is the raw material: your best objection handling already exists, spoken aloud by your best reps; extraction is how you find it.
Phase 3: Clustering into canonical objections
cluster.py embeds each record's objection text and assigns it to the canonical objections in state/objections.json: within threshold, increment that objection's evidence count; beyond threshold, hold out, and when holdouts form a dense group, propose a candidate objection for a human to confirm and name. Stable IDs, running centroids, candidate confirmation as a one-line PR, periodic full re-cluster with ID remapping: this is exactly the machinery from the anchor playbook, so go read that if the identity problem is new to you.
One wrinkle is specific to this workflow: call objections and competitor review complaints cluster together on purpose. When "your product is hard to migrate to" from your calls lands in the same cluster as fifty Trustpilot reviews calling Competitor X's migration a nightmare, that's the response writing itself: the objection and the public evidence that the alternative is worse, in one evidence pack.
The output per canonical objection is a small evidence pack in runs/<date>/: verbatim examples, frequency and trend, competitors named, the rep answers from won deals, and matching competitor reviews. A pack is 2-5K tokens. That's what the drafting agents read.
Phase 4: Drafting and the approval loop
Three agents, one human, and the human has the only merge button.
drafter.md takes one objection's evidence pack plus the positioning/ folder and writes or revises the response file. Its prompt states the grounding rule bluntly: every product claim must come from positioning/ or from a verbatim winning-rep answer in the pack; if the evidence suggests a claim that no source supports, write "CLAIM NEEDED" and flag it rather than invent it. Output lands as a draft file or as a supersedes: draft next to an approved one, never as an edit to approved content.
critic.md is the judge loop, and here it's doing verification, not style review:
Verify the draft response:
1. List every factual claim about OUR product. For each, cite the
exact positioning/ file and line, or the call ID of a verbatim
rep quote from a won deal. Any claim with neither source: FAIL.
2. List every claim about a COMPETITOR. Each must cite a specific
review record (source + date) in the evidence pack. FAIL otherwise.
3. Grep every "verbatim" quote against the source records. Paraphrase
presented as quote: FAIL.
4. Check pricing figures against positioning/pricing-onepager.md.
5. Does the draft contradict any currently approved response? Flag it.
Return PASS or FAIL with the specific line and missing source.
A hallucinated product claim in a battlecard doesn't just embarrass you: a rep repeats it, the prospect fact-checks it, and the deal and the library's credibility die together. The critic runs before any PR opens, maximum two revision loops.
The human approver reviews the PR like a code review: are the sources real, is this the answer we want reps giving, does legal care about the competitor claims? Merging sets status: approved, approved_by, and approved_date. Git gives you the rest free: blame shows who approved what and when, history shows how OBJ-014's response evolved as the market moved, and a revert is one command.
extractor.md is the quiet third agent: after each extraction batch it samples records against source transcripts and grades the extraction, so schema drift in your calls (new product names, new competitor) gets caught before it corrupts the clusters.
Phase 5: The weekly refresh
The /objection-refresh skill encodes the routine: read state/ and the last run, run export, extract, and cluster scripts, then fan out drafters only where something changed: new candidate objections, approved responses whose objection surged in frequency, and responses flagged stale. Staleness flags come from a simple check in the skill: if a record mentions a competitor price or feature that contradicts what an approved response says, flip that file's status to stale and open an issue. (Detecting competitor changes proactively, rather than from your own call mentions, is a separate competitor-monitoring workflow that chains naturally into this one; we'll cover it in a future playbook.)
Scheduling is the same choice as every playbook in this series. The managed path is a weekly cloud routine via /schedule: it clones the repo, inheriting state/, library/, and positioning/ automatically, and pushes drafts on a claude/ branch, which is precisely the PR-shaped output the approval workflow wants. Continuity across runs is the committed state, nothing more mysterious: watermark, registry, and library are all in git, and reading them is step one of the skill. The self-hosted alternative is cron plus headless mode (claude -p "/objection-refresh" with --allowedTools scoped to the pipeline) or a GitHub Actions cron with claude-code-action. Add a Stop hook posting run status to Slack so a silent failure doesn't cost you three weeks of coverage.
Phase 6: Sharing with the team
The library in git is the source of truth, but reps don't live in git. Three delivery layers:
- The repo itself for PMM and enablement: markdown is grep-able, and the frontmatter makes "show me every approved pricing objection touching Competitor X" a one-liner.
- An artifact page for browsing: have Claude Code render the approved subset (never drafts) as a single searchable page and publish it. Standard caveats apply: publishing works from the CLI and desktop app, not from every automated surface, so in a fully scheduled setup the publish step may stay human-triggered; org-scoped visibility needs a Team or Enterprise plan.
- Slack lookup, if reps ask for it. A small bot or workflow that takes "objection: too expensive" and returns the approved response is a genuine build (there's no official Slack MCP server; korotovsky/slack-mcp-server is the notable community option for the messaging side). Build it after the library has proven itself, not before. A stale library with a slick Slack interface is worse than a fresh one behind a URL.
Phase 7: Maintenance (what you own now)
- Approval backlog hygiene. Drafts nobody reviews pile up, and the library's freshness quietly becomes the approver's queue depth. Make the weekly Slack summary lead with "drafts awaiting review: N, oldest: 12 days" so the backlog is visible beyond the approver.
- Positioning-doc updates ripple. When messaging changes, every approved response citing the old positioning is suspect. The refresh skill should diff
positioning/against last run and flag every response whose cited source changed. This is why the docs live in the repo. - Competitor moves trigger re-reviews. A pricing change at Competitor X can invalidate five responses at once. Until you build the monitoring workflow, this is a human noticing and flipping statuses to
stale; assign it. - Transcript coverage gaps. If only half your reps record calls, objection frequencies are biased toward the recording half, and the library over-fits their deals. Track coverage (calls processed vs. calls held) in every run so nobody mistakes a sampling artifact for a trend.
- Plus the standing items from the anchor playbook: extraction spot-check audits, threshold tuning, candidate-objection triage, schema versioning.
What this costs
Assumptions stated so you can rerun the math: 30 calls/week at ~10K tokens, a 3,000-review corpus, one backfill.
- Extraction: ~300K tokens/week ongoing, a few million once for backfill, on a small model through the Batch API. This is metered API spend, and at this volume it's small: the weekly increment costs well under what you'd notice, and even the backfill is a single modest line item. Whatever your exact rates, this is 100x less volume than the conversation-intelligence workload, and it shows.
- Drafting and critique: agents reading 2-5K token evidence packs plus the positioning folder, only for objections that changed. A handful of drafter/critic runs per week fits comfortably inside subscription usage.
- Plan tier: routines and org-scoped artifact sharing carry the usual Pro-and-up / Team-and-up requirements.
- The real invoice: an approver who reviews PRs weekly, a positioning folder someone keeps current, and the maintenance duties above. This system's scarce resource is not tokens, it's the named human whose sign-off makes the library trustworthy. Budget their hours first.
The four practical challenges
- Setup at scale: the extract-cluster-summarize architecture from the anchor playbook, sized down (a 3-6M token backfill still cannot meet a context window directly), plus the governance layer that's unique here: approval frontmatter, PR-only changes, agents that may never touch approved content.
- Sharing with the team: git for enablement, an artifact page for browsing (with the automation and plan-tier caveats), Slack lookup as an earned second step. Delivery is easy; keeping reps' trust is the delivery problem that matters.
- Maintenance: approval backlogs, positioning ripples, competitor-triggered staleness, transcript coverage bias. Every one of these is a human duty with a name on it, and the library is exactly as alive as those duties are.
- Cost: the cheapest workflow in this series to run, single-digit-scale extraction spend and subscription-covered drafting, which means the real cost is almost entirely the approver's time. That is also the feature: you are paying for trust, and trust is the product.
The managed alternative
Two managed categories touch this workflow without owning it. Call intelligence (Gong) surfaces objections from your calls and tracks competitor mentions, but the curated, approved response library and its governance stay your job. Competitive enablement (Klue, Crayon) maintains battlecards, which overlap with the competitor-sourced half of the library but aren't grounded in your own call evidence. Most teams that buy both still end up with a wiki page of responses nobody versions, which is exactly the gap the build above closes.
Doing this with Second Axis
Everything above is the do-it-yourself path. On Second Axis, the objection library is a workflow in the marketplace: connect your call recorder and review sources, choose the workflow, and set the goal ("a versioned, approval-gated objection-response library, refreshed weekly"). It runs on schedule with the extraction, clustering, drafting, and approval flow built in, and it monitors itself: a stale source, a failed run, or responses waiting too long in the approval queue get flagged and alerted, so the library stays something sales can actually trust.