Debugging wrong CRM AI answers

· 7 min read· Salesforce · HubSpot

Your CRM AI gave a confident wrong answer. A symptom-to-cause index for the twenty failures that actually happen, what each one means, and the rule that prevents it recurring.

Something asked your CRM AI a question and got an answer that was wrong. Not obviously wrong, or you wouldn't be here. Wrong in the way where the number looked plausible, the prose was confident, and somebody almost put it in a board deck.

This page is the diagnostic. Find the symptom, get the cause, write the rule that stops it recurring.

The short version of why this happens: the model can read your data but not the rules around your data. It sees account_health = Green and a field name that sounds official. It cannot see the Slack thread where everyone agreed to stop maintaining that field. So it fills the gap with a reasonable assumption, and reasonable assumptions produce answers that look right.

First: is it wrong, or answering a different question?

Before hunting a cause, separate two failures that need completely different fixes.

Ask the agent to state the exact query it ran, the date range it used, and the exclusions it applied. Then run that query yourself.

  • Your query returns the same number. The model reasoned correctly from inputs nobody explained to it. That's a context problem, and the rest of this page applies.
  • Your query returns a different number. The model built the wrong plan. That's a prompt or retrieval problem. Check what guidance actually got loaded before you go writing new rules.

If the agent can't tell you what query it ran, fix that first. An agent that can't show its work can't be debugged, and disclosure should be an instruction, not a hope.

The failure-mode index

Twenty failures, the cause of each, and the rule category that prevents it. Symptoms first, because that's what you have.

SymptomWhat's actually happeningCategoryFix
Quarter numbers are close but never match the finance reportCalendar quarters used instead of your fiscal calendartemporalOne always-loaded rule with your fiscal boundaries and how the year is named
Year-over-year comparisons are subtly offSame fiscal drift, compounded across two periodstemporalSame rule; require the agent to state the exact date range
Pipeline total is meaningless but looks fineMixed currencies summed as one numbercurrencyConvert to reporting currency, or state which single currency the total covers
Counts are inflated and nobody knows whyTest, CPQ, and internal records counted as realexclusionAlways-loaded exclusion carrying the literal filter predicate
Deal credit goes to the wrong person, consistentlyTwo roles collapsed onto one owner fieldattributionName both roles and the field each comes from
A breakdown looks valid and every number is wrongGrouped by a multi-select; buckets are stored combinationsanti_patternSplit values before counting. Never group the raw field
Grouping fails, mis-groups, or gets worked aroundFormula or calculated field used in an aggregateanti_patternAggregate the underlying stored field instead
A conclusion is built on a field nobody uses anymoreDead field read as live because the name sounds officialauthorityDeprecation prefix plus one global rule covering the whole tail
Stale status trusted over a maintained scoreField label sounded more important than the real signalauthorityMark the stale field non-authoritative and name its replacement
A generated summary gets cited as verified factAI-written field treated as a sourceauthorityauthoritative: false on every generated field, with instructions to verify
"Next step: send proposal" resurfaces later as "proposal sent"Generator promoted intent to completed factauthorityInstruct that statements about completed actions are least reliable
Two people get different totals for the same questionQuery runs as the asking user; each sees a partial orgsecurityState whose permissions ran. Never conclude "none exist" from an empty result
A field is blank and the agent reasons from the blankFLS suppressed the field; null is indistinguishable from emptysecurityWITH SECURITY_ENFORCED so it errors instead of returning null
The agent answered from an object you didn't expectCouldn't reach the right object, substituted a similar namesecurityEnforce the object whitelist in code, not in the prompt
Funnel counts drop records that clearly existPopulation moved objects mid-lifecycle; query lives on onecross_object_dependencyShip the join, not just the observation that conversion happens
A rule applies on one object but not the related onePlan grew a second object after guidance loadedcross_object_dependencyFetch rules mid-plan, before querying any newly introduced object
The same question returns a different shape each timeNo canonical structure for a recurring answeroutput_shapeEncode the standard view as numbered rules plus taxonomy
An update "worked" but the value revertedField is force-set by automation on every savewrite_policywritePolicy: force_set_by_automation; don't recommend writing it
The AI overwrote a rep's qualification scoreHuman-owned field treated as agent-writablewrite_policyauthoritative: human_owned — compare and report, never overwrite
Answers changed after a "small" guidance editTwo active rules now contradict; retrieval order decidesdefinitionSet priority explicitly and validate for conflicts

The four that leave no trace

Most of the table gives you something to notice. These four don't, which is why they deserve separate attention.

A permission-suppressed null. Field-level security hides a field and the platform returns null rather than an error. There is no signal. The model reads "this account has no renewal date" and reasons from there. My favorite gotcha on the whole list, and the reason WITH SECURITY_ENFORCED is worth the noise.

A missing exclusion. An agent that doesn't know an exclusion exists cannot decide to fetch it. There's no gap for it to notice. It runs the query, gets a number full of test records, and reports it confidently. This is why exclusions always load and are never lazy-fetched.

A silently reverted write. The API returns 200. The value reverts minutes later when automation fires. Everyone downstream believes the recommendation was applied. Worse than a refusal, because a refusal is visible.

A field-history entry that never existed. Debugging note that costs people a day: the absence of a field-history row does not prove a field was never written. An async path can write and revert inside the same transaction window and leave no trace. Don't use "no history row" as evidence of anything.

Working one through

Take the most common report: "the agent said we created 214 opportunities last quarter, and the real number is 186."

  1. Get the query. Ask what date range and filters it used. Say it reports 1 April to 30 June and no exclusions.
  2. Two causes are already visible. Your fiscal Q1 doesn't run April to June, and it applied no exclusions at all.
  3. Confirm before writing rules. Run the count for your real fiscal range with your standard exclusions. If you get 186, both causes are confirmed.
  4. Write the rules. One temporal rule with your fiscal boundaries, one exclusion rule carrying the literal predicate. Both alwaysLoad: true.
  5. Record why they exist. Set originFailure on each to this report. In six months, that's the difference between a reviewable library and a pile of assertions.
  6. Add a golden question. "How many opportunities did we create last quarter?" with pass criteria: states the exact date range, names the exclusions applied, and excludes system-generated records.

Step 6 is the one people skip, and it's the one that makes the fix permanent. The rule stops this failure today. The golden question stops it coming back when somebody changes retrieval logic four months from now.

When the answer is right but you can't tell

Sometimes nothing is wrong and you have no way to know that, which is its own problem.

Make the agent show its work by instruction, not by request. Every answer should state the rules it applied, the exclusions it applied, the exact date range, and whose permissions the query ran under. That turns an unverifiable paragraph into something a skeptical person can check in two minutes.

You'll catch more bugs from that habit than from any amount of prompt tuning.

Stopping the next one

Debugging one answer is worth doing. Building the loop is worth more:

  • Log the guidance version and rule IDs with every run, alongside the objects and fields the plan actually queried. You cannot write a corrective rule for a failure you can't reconstruct.
  • Turn every fix into a golden question, so the suite grows the same way the library does.
  • Deploy rule changes separately from retrieval changes. Ship both at once and you won't know which caused the next regression.

That's the whole mechanism: mistake, trace, rule, golden question, protected forever.

FAQ

Why does my CRM AI give confident wrong answers?
Almost always because it can read your data but not the rules around your data. It sees a field name and a value, and it cannot see that the field was abandoned last year, that your fiscal year starts in February, or that a third of those records are CPQ artifacts. So it fills the gap with a reasonable-sounding assumption, and reasonable-sounding assumptions produce answers that look right.
How do I tell whether the model is wrong or the data is wrong?
Ask it to state the exact query it ran, the date range it used, and the exclusions it applied, then run that query yourself. If your query returns the same number, the model reasoned correctly from bad or unexplained inputs, which is a context problem. If it returns a different number, the model got the plan wrong, which is a prompt or retrieval problem. These have completely different fixes.
Why do two people get different answers to the same question?
Because the connector is probably running as the asking user, so each query returns only the records that user can see. That's correct behavior and the model has no idea it's looking at a partial org. The fix isn't to run as an admin; it's to make the agent state whose permissions the query ran under, and never conclude that no records exist from an empty result on a sharing-restricted object.
The field is blank. Does that mean there's no value?
Not necessarily. If field-level security hides a field from the running user, the platform does not raise an error — it returns null, which is indistinguishable from a genuine blank. This is the nastiest failure on the list because there is no signal at all. Use WITH SECURITY_ENFORCED in Salesforce so the gap surfaces as an exception instead of an empty value.

Get the next guide

New guides and the occasional note on GTM tooling. Don't worry, I won't drop you into a three-month nurture.