A WordPress Plugin Conflict Debugging Checklist We Actually Use
The standard forum advice — deactivate every plugin, then turn them back on one by one — is the slowest…
At some point in almost every discovery call, a client says some version of “and we’d like AI in there too.” Sometimes they mean something specific — a chatbot like the one on a competitor’s site, or “smart” product recommendations. More often it’s shorthand for “make this feel current,” dropped in the same breath as “and it should work well on mobile.” Either way, “AI” is not a feature. It’s an opening for a conversation, and the conversation is where the actual work starts.
This is a note from that conversation: the question we ask to turn “we want AI” into something we can actually scope and estimate, the three ways the unscoped version breaks once it’s live, and a couple of times the honest answer was “you don’t need a model for this.”
The fastest way to turn a vague AI ask into a real spec is one question: what decision is this feature making, and who’s accountable when it’s wrong? Everything else — architecture, cost, review process — falls out of the answer.
“AI product recommendations” on a Shopify store and “AI customer support” on the same store are not variations of one feature. One is scored and ranked, and wrong in a way that basically nobody notices if it happens once in a while. The other is answering a real person who might act on what it says: cancel an order, expect a refund, believe a return policy that isn’t real. Same line in the brief, completely different systems, completely different review burden. If we don’t split those apart on the first call, we end up scoping the wrong one.
The most common version of this we see: a WooCommerce or Shopify store wants a support widget that “answers customer questions using AI.” Someone wires up an LLM with a system prompt — “you are a helpful assistant for this store, be friendly, don’t make things up” — and points it at the site. It works fine in the demo. Then a real customer asks about a discount code that doesn’t exist, or a return window that’s different from the actual policy, and the model answers fluently and wrong, because asking it not to hallucinate in the prompt doesn’t stop it from hallucinating. That instruction has no teeth — it’s one sentence inside a much larger block of text the model is only ever probabilistically weighting against everything else it knows.
The fix isn’t a better-worded prompt. It’s giving the model less room to talk. Instead of letting it generate free text about orders, refunds, or policy, we restrict it to tool calls against real data, and let it write natural language only around what the tool actually returned:
// the model can only ever state what this function returns —
// it cannot invent an order status, a refund amount, or a policy
{
"name": "get_order_status",
"description": "Look up the current status of an order by order number",
"parameters": {
"order_number": { "type": "string" }
}
}
// system instruction: "Only state facts returned by a tool call.
// If no tool covers the question, say you'll connect them to a person."
That one architectural choice — grounded tool calls instead of open generation, plus a hard fallback to a human for anything outside the tool’s coverage — does more than any amount of prompt tuning. For anything with real consequences (refunds, medical or legal-adjacent claims, price overrides) we also add a human review queue: the model drafts, a person approves before it goes out, at least until there’s enough logged history to trust it unsupervised on a narrower slice of questions.
The second failure mode is quieter and shows up on an invoice instead of a support ticket. A client wants “AI search” across a catalog, or a “regenerate description” button on every product, and the first version calls the model live, on every request, with nothing cached in front of it. Picture a product page that gets a few thousand views a day, where every view triggers a fresh embedding call or a fresh generation — that’s not an edge case, that’s just what happens by default if nobody explicitly designs a cost model in from the start.
The fix is boring, and it’s the same fix as any other expensive external call: cache aggressively, and don’t run live inference for anything you can precompute. Generated product descriptions get written once, reviewed once, and stored — not regenerated on every page load. Embeddings for search go into pgvector or a vector index once, at content-change time, not per query. And we tier models by stakes: a cheap, fast model in the GPT-4o-mini class handles high-volume, low-risk work like tagging and routing, and a stronger model like GPT-4o or Claude is reserved for the smaller slice of requests that actually need the extra reasoning.
// cache-first pattern for anything that doesn't need a live answer
async function getDescription(productId) {
const cached = await redis.get(`desc:${productId}`);
if (cached) return cached;
const generated = await llm.generate(productId); // only runs on a cache miss
await redis.set(`desc:${productId}`, generated, "EX", 60 * 60 * 24 * 30);
return generated;
}
We also put an explicit daily or monthly spend ceiling in front of any client-facing AI feature, with an alert — and ideally a graceful degrade, not a hard outage — when it’s approached. “Unbounded” is the actual failure here, not that the API is expensive. Nobody decided in advance what “too expensive” looks like, so nothing was built to notice when it happened.
The third one is about expectations, not architecture. A client asks for “instant AI search,” picturing a search-as-you-type box, and gets uneasy when a real LLM call takes a second or two — sometimes longer, if it’s doing retrieval first and generation second. That’s not a bug. A round trip to a language model is a genuinely different kind of operation than a database query, and nobody scoped the latency in the brief because “AI” sounded like it should just be fast by default.
Two things help. First, set the number early — run a real spike before promising anything, and put “this will typically answer in 1.5 to 3 seconds” in writing before the client sees it live. Second, use streaming so the perceived latency drops even when total latency doesn’t: the first tokens showing up in a few hundred milliseconds reads as fast even if the full answer takes a few seconds to finish. And honestly, for a good chunk of “AI search” requests, the right architecture doesn’t call a model at all — full-text or trigram search (Postgres pg_trgm, MySQL FULLTEXT) handles “find products matching this text” instantly and deterministically, and the model only gets involved for the harder fallback: a genuinely ambiguous or conversational query that keyword search can’t parse.
We don’t default to recommending a model. For a catalog under a few hundred SKUs, a rules-based “frequently bought together” table usually outperforms a learned recommendation model, costs nothing to run, and a store owner can look at it and understand exactly why a given pairing shows up — which matters the first time they ask. For support-heavy builds, we’ve also moved away from reaching for a full custom RAG pipeline as the default first answer; a well-organized FAQ with decent search covers most of the same ground, is far cheaper to maintain, and doesn’t carry the review overhead of an AI answering path that can go wrong in public. The model earns its place when the problem is genuinely open-ended — free-text questions, unstructured content, judgment calls at a volume no human team could cover — not by default because the brief happened to use the word.
The actual deliverable from that first conversation isn’t a slide with “AI-powered” on it. It’s a short written scope that answers three things: what decision the feature is making and who’s accountable when it’s wrong, what happens to cost and latency at real volume instead of demo volume, and where the fallback to a human or a deterministic system kicks in. Everything past that is implementation.
Tell us what you're building and we'll tell you honestly whether we're a fit.
The standard forum advice — deactivate every plugin, then turn them back on one by one — is the slowest…
Self-hosted n8n is easy to stand up and easy to neglect once it's quietly running a client's order pipeline. Here's…
Checkout.liquid died in two waves — August 2024 for the core checkout, August 2025 for Thank You and Order Status…