Why a duplicate rule slipped through and triggered chaos
Every time I think I’ve finally nailed a stable AI-driven automation flow, something breaks in a way that makes absolutely no sense. Especially when you’re mixing conditional logic with AI model calls — things that seem harmless (like a duplicate filter or a stray webhook response) can spiral out fast. Here’s a walkthrough of how I recently rebuilt a flow that started off sensible and ended up firing six parallel duplicate Slack alerts for the same data pull .
Before we dive in, I should say: most of this chaos came from Zapier and Make running identical logic inconsistently, depending on the outputs of ChatGPT and whether or not a Google Sheet cell was exactly empty vs just whitespace. None of that was obvious in the UI. You know the drill.
1. Mapping AI responses when your source data is a moving target

Let’s say your flow starts with an Airtable record being updated. That record contains a bunch of customer support notes, and now you want OpenAI to summarize it — automatically — and send the summary to a Slack channel, with urgency based on tone.
From a human’s perspective, this seems logical: if the text contains angry words, use a red flag emoji and notify the escalation team. Otherwise, keep it simple. Easy, right? Ha. 😛
Except… Airtable randomly triggers updates even when data doesn’t change. You’re probably grabbing both the old and new values in Zapier’s trigger, but Zapier doesn’t clearly differentiate null vs empty string, and if you’re comparing those in a filter (e.g., `Field X has changed`), you can end up re-processing the same AI call multiple times. That’s how we ended up burning through our OpenAI tokens for duplicate requests. $5 here, $5 there…
Then there’s the delay. If you’re calling GPT-4 in a Make.com flow, OpenAI might return the summary in 4 seconds or 40, depending on the prompt token count and load. That output often looks like:
“
“Summary: The customer is frustrated about repeated billing issues.”
“Tone: Frustrated”
“
Make doesn’t like multi-paragraph responses unless you manually parse them. I used a Text Parser module to split on “Tone:” but if GPT accidentally adds an extra colon (“Tone: Frustrated:”), the split fails. No error message — the parser just returns null, and the flow silently exits.
So yeah: depending on whether GPT uses a bullet list, numbered list, or a stray newline, your downstream path can silently skip over the escalation logic.
If the AI response format is inconsistent, then the conditional logic based on those fields is bound to break randomly. The fix? I ended up pre-validating the output with a regex before parsing. You shouldn’t have to regex your way around a model that’s supposed to be smart — but here we are.
2. When two filters both pass and launch parallel branches anyway
This one took me a full hour to identify because the issue only happens intermittently, and only with certain AI inputs.
In one Make.com scenario, I set up routing where:
– Route A contains a filter: “If Tone = Angry”
– Route B contains a filter: “If Tone ≠ Angry”
So naturally, only one path should run depending on the GPT-tagged tone… right? NOPE.
There’s a weird behavior in Make where if the incoming value contains formatting (e.g. trailing spaces, newline characters, or even lowercase versus uppercase), both routes can pass — because the comparison filter ends up treating the input as unknown. For example:
– GPT returns “angry\n”
– Route A compares `value = Angry`
– Route B compares `value ≠ Angry`
Because of a newline, both conditions are treated as partially true. So both paths execute. You get two Slack pings instead of one.
Make docs warn about whitespace issues *elsewhere*—but there’s zero visible warning here. I had to insert a Transform module after the AI step just to trim the value. Once that was in place, routing started behaving normally.
Am I bitter about losing an hour to debugging invisible spaces? Yes. Yes, I am. 🙂
3. Prompt inconsistency causes tiny logic splits that balloon downstream
I had this idea where we’d use GPT-4 to read a Notion document submitted by our marketing team, auto-categorize it as “announcement,” “how-to,” or “thought piece,” then set up publishing rules accordingly.
The Notion trigger → OpenAI prompt worked okay. But the classification wasn’t consistent: the *same* document, when tested three times in Make or Zapier, would get different categories.
Why? Turns out the temperature setting inside Zapier was defaulting to 1.0, but in Make, I had manually set it to 0.3. Consistent inputs were producing different outputs just because one field was hidden by default in Zapier’s interface.
So our publishing flow went haywire:
– Week 1: Articles got flagged as “how-to” and published to the knowledge base
– Week 2: Identical articles were tagged “thought piece” and sent to editorial
– Week 3: Same doc classified as both and routed through *both* flows due to identical keywords
After forcibly injecting a system message (“You must consistently classify based on these three examples…”) and hardcoding the prompt format, I was finally able to get deterministic output.
One practical takeaway: don’t trust temperature defaults. Always set it *explicitly*, even if the UI says it’s optional.
4. One missing webhook test leads to message loop hell
I don’t usually test webhooks in isolation once the upstream and downstream flows are semi-stable. Mistake. Here’s what happened:
I had a flow in Zapier where:
– A webhook receives a new deal
– The deal gets summarized using OpenAI
– That summary is written back to a CRM via another webhook
– When the CRM gets updated, it fires another webhook (same endpoint)
I *thought* the filters would prevent loops, because I assumed “Updated by this flow” would block re-entry. But some CRMs (this one was Close.com) don’t distinguish between user-updates and API-updates.
So this happened:
1. Zap fires on webhook A
2. Triggers GPT and writes to CRM
3. CRM sends webhook B
4. B writes back to CRM
5. CRM sends webhook C
Repeat until Slack gets flooded and the CRM blocks external writes.
The only indication that something was wrong was a repeating summary that kept saying things like:
“Summary of previous summary: The deal includes a note about pricing changes.”
Eventually, GPT was summarizing its own summaries, and at one point, it said:
“This appears to be an automated loop.”
— You think??
I fixed it by writing a router that skips input if “Note contains: Summary generated by AI” — but if the CRM strips rich text formatting, even that fails.
5. Random AI output formatting clobbers your naming logic
Let’s say you’re having ChatGPT generate file names based on content titles. You want to auto-name documents before uploading them to Google Drive so you can skip all that manual renaming.
So your prompt looks like:
“Based on this title, return a sanitized filename: {{title}}. No spaces, snake_case only, max 50 characters.”
Sometimes you get:
`”file_name_based_on_topic_title.txt”`
And other times:
`Filename: file_name_based_on_topic.txt`
That colon will break your naming flow unless you explicitly parse it out. Even worse, Google Drive will reject names with unsupported characters (like slashes, question marks), and your Zap won’t throw a visible error — it just fails silently unless you’ve added a Catch module.
I had multiple files “upload” but then not appear in the target folder. You know the worst part? Zapier’s run history *said* the file was created. But it wasn’t.
Solution? Add a GPT output sanitizer that scrubs punctuation, limits character count, and strips quotes. Sounds tedious, but it’s better than filing help tickets with vague screenshots and no log errors.
Quick checklist that saved me later:
– Always ask GPT for *only the value*, not metadata (no “File name:”)
– Sanitize forbidden characters before uploading
– Add a file-exists check before writing
– Never use titles as-is unless you regex the crap out of them
6. AI retry logic does not preserve prior state correctly
Had a flow with OpenAI generating customer response templates. If the first template contained unsupported language (some clients have content safety filters), we’d auto-retry with a stricter tone.
The problem: Zapier’s built-in error handler can retry a step, but it does *not* reset data inputs from prior steps. If the retry is deeply nested in a path that’s already mutated variables (say, you’ve changed the “tone” or “language preference” midway), the retry reuses the *mutated* input, not the original.
It created this awful case where:
1. GPT generates an assertive message
2. Message fails content filter (too harsh)
3. Retry triggers — but with same assertive ‘tone’ as input again
Basically the exact same message gets sent through again, filtered again, looped again. Fun.
Eventually, I resolved this by saving original state in an isolated text column (e.g., `initial_tone`), and all retries pulled from that instead of whatever was last mutated. Not exactly elegant, but at least it stopped the infinite retries.
Honestly, retry logic with memoryless AI tools is a landmine. Zapier doesn’t snapshot step context — it replays steps as if nothing changed — so if you’re not simulating memory yourself using intermediate storage (Google Sheets, Airtable, etc), unpredictable duplication happens if anything downstream mutates state.
¯\_(ツ)_/¯
