Troubleshooting
Why No-Code Stacks Break at Scale: Failure Modes of Zapier, Make, Bubble, Airtable & Supabase (With Fixes)
No-code stacks don't break because the tools are bad. They break because every tool has hidden limits - payload caps, type coercion, permission models, rate limits - that stay invisible until volume finds them. This is a failure-mode catalog: the real errors founders hit (the exact strings you just Googled), what actually causes each one, and the genuine fix. Every section follows the same shape: symptom → cause → fix. And if you'd rather never debug this class of problem again, that's what automation rescue exists for.
Why This Keeps Happening
The pattern is always the same. You build an automation at 10 records a day and it works perfectly. Six months later it's processing 1,000 records a day and a limit you never knew existed - a 100kb payload cap, a per-second rate limit, a string being treated as an array - takes down a workflow your revenue now depends on. The tool didn't change. Your scale did.
Below, tool by tool. Use your browser's find function for your exact error string.
Airtable Failure Modes
Script block: "webhook payload exceeds 100kb" on loop triggers
Symptom: An automation that fires a webhook per changed record works fine - until a bulk paste, CSV import, or scripted batch update triggers it across hundreds of records and the run fails with webhook payload exceeds 100kb.
Cause: Airtable caps webhook payloads at 100kb. Loop-style triggers bundle every changed record's data into a single payload, so batch operations blow straight through the cap.
Fix: Stop shipping record contents through the webhook. Send record IDs only (or a bare ping) and have the receiver pull full records through the Airtable REST API with pagination. If you control the upstream writes, chunk them - 50 records or fewer per batch keeps each trigger under the cap.
Or never debug this again: HireWilliam's automation rescue rebuilds these triggers with proper ID-based handoffs and paginated fetches as standard practice.
Interface Designer: edit-layout button missing
Symptom: A collaborator opens an interface and the edit/layout controls simply aren't there - it looks like a bug, and team members assume the interface is broken.
Cause: It's a permissions cliff, not a bug. Editing interfaces requires Creator-level base permission (and on some plans, owner-level for certain interface features). Editor-level collaborators see the interface but never the layout tools, with no explanatory message.
Fix: Check the collaborator's role on the base (not the workspace) - bump them to Creator if they should edit. If you're a Creator and still missing controls, check whether the interface is in published vs. draft state; published interfaces hide editing until you re-enter the builder. The deeper issue: Airtable's permission model has at least four layers (workspace, base, table, interface) and they fail silently. Document who owns what, or have us do it during a rescue.
Make.com Failure Modes
Variable from the Airtable module arrives blank in the next step
Symptom: The Airtable module clearly returns data in its output bundle, but the mapped variable shows up empty in the following module.
Cause: One of three: the field is a collection (array) mapped as a scalar, which Make renders as blank rather than erroring; the mapping pill points to a stale module reference after you edited or re-ordered the scenario; or the field is genuinely empty at trigger time - Airtable lookups and rollups can lag a trigger by a few seconds.
Fix: Run only the Airtable module and inspect the raw output bundle. Array where you expected a value → add an Iterator or wrap with first() / map(). Stale pill → delete the mapping and re-select from the live module output. Lagging computed field → add an existence filter or a short sleep-and-refetch.
"Cannot read properties of undefined (reading 'map')" in custom apps and devtool extensions
Symptom: A custom Make app, code module, or a Make-related Chrome devtool extension throws cannot read properties of undefined (reading 'map').
Cause: JavaScript called .map() on a value that's undefined - an API response whose shape changed (error object or empty body where an array was expected).
Fix: Look at the actual response in the execution log or devtools Network tab, then guard the access: data?.items ?? [] before mapping. If it's a third-party extension throwing on your page, disable it to confirm, then replace or report it - the extension is choking on your data shape, and that's its bug, not yours.
Zapier Failure Modes
Loop splits a text input into individual letters
Symptom: Looping by Zapier runs once per character - "Acme" becomes four loop iterations: A, c, m, e.
Cause: You fed the loop a plain string. Strings are iterable, so Zapier dutifully iterates character by character. The loop needs line items (a real array), not text.
Fix: Insert Formatter → Utilities → Text to Line-item, splitting on your real delimiter (comma, newline, pipe), and loop over the line items. If the source is a webhook delivering a JSON array that arrived stringified, a Code step with JSON.parse() restores the array before the loop.
Webhook returns 415 "unsupported media type: text/plain" from Node.js
Symptom: Your Node.js service POSTs to a Zapier (or other) webhook and gets 415 Unsupported Media Type, noting the content type was text/plain.
Cause: Passing a plain string body to fetch or axios without headers defaults the Content-Type to text/plain. The endpoint only accepts application/json.
Fix: Set the header and stringify: fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }). With axios, pass the object itself - it sets the JSON header for you. If you truly need to send raw text, use a "Catch Raw Hook" trigger that accepts any content type.
Or never debug this again: brittle hand-rolled webhooks are one of the most common things automation rescue replaces with validated, retrying integrations.
Bubble Failure Modes
Data source expression shows red "not a valid type" errors
Symptom: An expression that used to evaluate now glows red with ...is not a valid type, usually after editing a data type or a reusable element.
Cause: Bubble expressions are typed end-to-end. If you renamed or deleted a field, changed a field's type, or a reusable element's content type no longer matches what the expression returns, every downstream expression breaks - and Bubble shows the error at the point of use, not the point of change.
Fix: Read the expression left to right and find where the chain's type stops matching - the issue tracker (the red exclamation panel) lists every broken expression after a schema change. Re-point the broken segment at the renamed field or add the missing conversion (e.g. :converted to text, or a search constrained to the right type). After any data-type change, sweep the issue tracker before deploying; Bubble will happily run a broken page in production.
Preview mode shows a blank screen (especially in Brave)
Symptom: The editor works, but preview renders a white page - often only for some team members.
Cause: Tracking blockers. Brave Shields, uBlock, and some privacy extensions block scripts Bubble's preview runtime loads (analytics bundles, third-party plugin scripts), and a single blocked critical script can blank the page.
Fix: Drop Brave Shields (or the ad blocker) for the preview domain and reload - if it renders, you've found it. Long term: audit your installed Bubble plugins for ones that inject trackers into the page head; every marketing pixel a plugin injects is another thing privacy browsers will block for your real users too, not just in preview.
Webflow Failure Modes
Dynamic multi-reference fields blank after a Make scenario writes them
Symptom: A Make scenario creates or updates CMS items; single fields land fine but multi-reference fields render empty on the site.
Cause: Multi-reference fields expect an array of referenced item IDs from the same collection, and Webflow's API silently drops invalid payloads - a comma-separated string, slugs instead of IDs, or IDs from a draft/unpublished item all "succeed" and store nothing. Items written via the API also sit unpublished until you publish them, so referenced drafts render blank.
Fix: Send a true array of valid item IDs (in Make, use an array aggregator, not a joined string), confirm every referenced item is published, and publish the updated item itself via the API's publish step or a site publish at the end of the scenario.
Localized CMS items not syncing
Symptom: You update a CMS item and the localized versions keep showing old content - or new items never appear in secondary locales.
Cause: Webflow Localization treats each locale's CMS content as an override layer. Once a locale has overridden a field, primary-locale edits no longer cascade to it; and API writes target the primary locale unless you explicitly address the locale.
Fix: For fields that should always mirror the primary, remove the locale override (reset to inherit). For API workflows, write to each locale explicitly using the locale-specific endpoints. Then re-publish - locale content has its own publish state, and an unpublished locale is the most common "it's not syncing" in practice.
Supabase Failure Modes
RLS infinite recursion on policies that select from auth.users
Symptom: Queries fail with infinite recursion detected in policy for relation ...
Cause: A row-level security policy runs a subquery against the same table it protects (or a view that re-triggers that policy) - the classic case is a members/profiles policy that checks membership by selecting from itself, or policies that try to join through auth.users.
Fix: Break the cycle one of three ways: (1) move the lookup into a SECURITY DEFINER function - it bypasses RLS for the internal query - and call the function inside the policy; (2) decide from JWT data instead: auth.uid() and auth.jwt() claims need no table read at all; (3) split role/team data into its own table whose policies don't reference back. And don't query auth.users from client-reachable policies - maintain a public profiles mirror via trigger.
Google OAuth fails with a redirect code mismatch (notably in Arc)
Symptom: Google sign-in works in Chrome but fails for some users - especially Arc browser - with a code/state mismatch or "invalid flow state" on the redirect back.
Cause: The PKCE flow stores a code verifier in the browser before redirecting to Google; if the redirect returns to a different context than the one that started it (Arc's workspace/profile handling, in-app browsers, or strict cookie partitioning), the stored verifier is missing and the code exchange fails. A redirect URL that hops between www and apex domains triggers the same thing.
Fix: Make sure the OAuth flow starts and ends on the exact same origin - one canonical domain in your Supabase auth redirect allow-list and in the Google console. Use the current supabase-js (it handles verifier storage more robustly), and ensure storage isn't partitioned away mid-flow. For stubborn embedded-browser cases, detect and prompt users to open in their default browser.
goTrue custom verification emails redirect to the wrong place
Symptom: You customized the confirmation/magic-link email and users now land on localhost, the Supabase default domain, or a page that says the link is invalid.
Cause: goTrue builds links from the project's Site URL plus an allow-listed redirect_to. Custom templates that hardcode URLs, a Site URL still set to localhost from development, or a redirect_to that isn't on the allow-list all fall back to defaults - and one-time tokens expire on first click, so email clients that prefetch links can consume them.
Fix: Set Site URL to production, add every legitimate redirect target to the Redirect URLs allow-list, and use the {{ .ConfirmationURL }} template variable rather than hand-built links. If corporate email scanners eat the one-time link, switch to OTP codes in the template instead of links.
Schema drift errors during early-stage Postgres migrations
Symptom: Migrations that worked locally fail in production with mismatched columns, or supabase db push complains the remote schema differs from your migration history.
Cause: Schema drift - someone edited the production schema through the dashboard's table editor while migrations were also in play. Now the migration history and the real schema disagree.
Fix: Pick one source of truth. Run supabase db diff against production to capture the drift as a new migration, commit it, then enforce a rule: all schema changes through migration files, the dashboard editor is read-only from now on. For teams already deep in drift, supabase db pull to re-baseline, then migrate forward only.
Stripe Failure Modes
Checkout/billing-portal redirect loop with a custom domain + Auth0
Symptom: The customer finishes checkout or the billing portal, returns to your app, gets bounced to Auth0 login, then back, then to login again - a loop.
Cause: Session-cookie mismatch. Stripe returns the user to your return_url, but that URL is on a different scheme or (sub)domain than the one holding the Auth0 session cookie - www vs apex is the classic - so the app sees them as logged out.
Fix: Make success/cancel/return URLs match the logged-in origin exactly; set Auth0's cookie at the parent domain (.yourdomain.com) so it survives subdomain hops; add the return URL to Auth0's Allowed Web Origins so silent re-auth can recover the session. If you've enabled a Stripe custom checkout domain, remember the billing portal return URL is configured separately - update both.
Payment link metadata not reaching webhooks
Symptom: You set metadata on a Payment Link, but your webhook handler sees empty metadata.
Cause: Metadata doesn't cascade in Stripe. Payment Link metadata lands on the Checkout Session - not automatically on the PaymentIntent, the Subscription, or the Invoice your webhook may be reading.
Fix: Listen for checkout.session.completed and read metadata from the session object, or expand the session from whatever object you received. For subscriptions, set subscription_data.metadata so it propagates onto the subscription itself. Rule of thumb: put metadata on the object whose webhook you actually consume.
Stripe Tax automated calculation fails for a province (e.g. Ontario) with custom metadata
Symptom: Stripe Tax returns a calculation error - or silently applies the wrong rate - for a Canadian province like Ontario, even though tax works fine for other regions.
Cause: Stripe Tax needs a fully resolvable customer address (country + state/province + postal code) and an active registration for that jurisdiction. Passing province in a custom metadata field instead of the structured customer.address.state means Stripe never sees it; missing a Canada/Ontario tax registration makes it refuse to calculate.
Fix: Populate the structured address object (use the two-letter subdivision code, ON for Ontario) rather than stuffing it in metadata, add your Canadian tax registrations under Stripe Tax settings, and confirm the product has a tax code assigned. Verify with a test customer at an Ontario postal code before going live.
PostHog Failure Modes
Autocapture breaks shadcn/Radix dropdown listeners
Symptom: After adding PostHog, shadcn/ui dropdowns misbehave - menus close instantly, onSelect doesn't fire.
Cause: Autocapture's document-level capture-phase listeners interfere with Radix's pointer-event sequencing across portal elements.
Fix: Upgrade posthog-js first - several releases addressed event interference. If it persists, add ph-no-capture to the trigger and menu content, or disable autocapture at init and send explicit posthog.capture() events for what you actually measure. Confirm causality by commenting out the PostHog init - if the menu works, you've isolated it.
Dashboard shows NaN% after a property update
Symptom: A previously fine insight or dashboard tile reads NaN%.
Cause: A formula or ratio insight is dividing by a series that now returns zero or no data - usually because an event or property was renamed, or a filter references the old property name, so the denominator went empty.
Fix: Open the insight, check each series returns data over the period, and re-point filters/formulas at the new property name. If you renamed properties at the source, add a mapping (or alias the old name in your capture code) for continuity - and annotate the dashboard with the change date so future-you doesn't re-debug it.
Rapid-Fire: More One-Liners
- Vanta compliance check fails on a Bubble static S3 asset: the audit flags your Bubble-hosted asset because the S3 bucket object is missing a required response header (often cache-control or a security header) that Vanta's check expects. You can't edit Bubble's bucket headers directly - front the assets with a CDN/proxy (Cloudflare) that injects the missing headers, or move the flagged static assets to a bucket you control.
- Deel onboarding stuck on tax-document collection: the worker's onboarding spins on the tax step when their country/entity type doesn't match the contract type, or a prior document is in a rejected/pending state. Confirm the contract type (contractor vs EOR) matches the worker's country, have them re-upload the exact document Deel asks for, and open a Deel support ticket referencing the worker ID if the step won't clear.
- Mercury Bank API 403 on a production payroll batch transfer: the 403 is an authorization scope problem, not a balance problem - the API token lacks send-money/treasury permission, the IP isn't allow-listed, or you're hitting prod with a sandbox key. Re-issue a token with payment scope, allow-list your server IP, and verify you're on the production base URL before retrying the batch.
- Retool on Safari mobile: custom CSS that works on desktop collapses on iOS Safari - Retool's mobile rendering doesn't honor all desktop container CSS. Use Retool Mobile-native components or test every custom-CSS view on an actual iPhone before rollout.
- Framer + Cloudflare proxied domain errors: Framer needs to issue its own SSL and verify the domain; Cloudflare's orange-cloud proxy breaks that handshake. Set the Framer DNS records to "DNS only" (grey cloud) - or you'll chase SSL and redirect errors forever.
- Softr form → Airtable linked fields: Softr forms can't write to a linked-record field with free text - they need the linked record to exist and be selected. Use a connected list/dropdown bound to the linked table, or write to a text field and link it with an Airtable automation afterward.
- Intercom widget vs Tailwind drawers: the Intercom launcher's high z-index iframe sits over your drawer's touch targets on mobile. Lower the launcher (Intercom supports custom
z-indexvia settings), reposition it, or hide it programmatically (Intercom('update', {hide_default_launcher: true})) while a drawer is open. - OpenAPI schema validation on deep-nested JSON arrays: validators reject deeply nested arrays when the spec doesn't define
itemsat every level - each array layer needs an explicititemsschema. Define nested levels fully (or use$refs for the repeated shapes) instead of leaving inner arrays untyped.
The Pattern Behind All of This
Read back through the catalog and one thing stands out: none of these are exotic bugs. They're known limits hit at scale, version drift, and silent failure modes - the operational tax of running a business on a dozen glued-together tools with nobody owning the glue.
You have three options. Keep paying the tax in founder hours. Hire an engineer to own the glue. Or hand the whole layer to a team that does this all day: HireWilliam's automation rescue takes over broken no-code stacks, fixes the failure modes above, rebuilds the brittle parts on monitored, retrying infrastructure, and then keeps it running - so you never debug a 100kb payload cap at midnight again. Across 245+ implementations, rescued stacks typically hand back 10-20 hours per week, and the fix is deployed in days, not months.
Send us the error you're staring at right now - we've probably fixed it before: email info@hirewilliam.com.
Frequently Asked Questions
How do I fix the Airtable script error "webhook payload exceeds 100kb" on loop triggers?
Airtable caps webhook payloads at 100kb, and a loop trigger that fires on many records at once (a bulk paste, a CSV import, a scripted batch update) bundles all the changed records into one payload that blows the cap. Fix: stop sending record contents through the webhook. Send only record IDs - or just a ping - and have the receiving automation fetch full records via the Airtable REST API in pages. Alternatively, throttle the producer: batch your upstream updates into chunks (50 records or fewer per write) so each webhook fires under the limit. If this automation matters to revenue, HireWilliam's automation rescue service rebuilds these triggers with proper pagination so you never debug a payload cap again.
Why is my variable from the Airtable module blank in the next Make.com step?
Three usual causes: (1) the Airtable module returned a collection (array) and you mapped it as a plain value - Make renders it blank instead of erroring; wrap it with map() or first(), or add an Iterator module; (2) you re-ran or edited the scenario and the mapped pill now points to a stale module reference - delete the mapping and re-pick the field from the current module's output; (3) the Airtable field is genuinely empty on some records (lookup and rollup fields are empty until Airtable computes them, which can lag a trigger by seconds) - add a filter that checks the field exists, or a sleep/retry. Run the scenario once with "Run this module only" on the Airtable step and inspect the actual output bundle - the shape of the data there tells you which of the three you have.
What causes "cannot read properties of undefined (reading map)" in Make.com or a Chrome extension?
That error means JavaScript called .map() on something that doesn't exist - an upstream step returned undefined where an array was expected. In Make.com custom apps or devtool extensions, it's almost always an API response whose shape changed: the code expects response.data.items and the API returned an error object or an empty body instead. Fix: inspect the raw response (Make's execution log shows each bundle; in Chrome devtools check the Network tab), then guard the code - use optional chaining (data?.items ?? []) or an explicit empty check before mapping. If a third-party extension throws it, the extension is failing on your page's data shape - disable it to confirm, then report or replace it.
Why is my Zapier loop splitting text input into individual letters?
Looping by Zapier iterates over whatever you give it - and in JavaScript terms a string is an array of characters. If you feed the loop a plain text field instead of a real array (or comma-separated values), it "helpfully" iterates character by character, producing one loop run per letter. Fix: convert the text into a proper list first. Use Formatter > Utilities > Text to Line-item (splitting on your actual delimiter - comma, newline, pipe), then feed the line-item output into the loop. If the data arrives from a webhook as a JSON array, make sure Zapier parsed it as line items rather than a stringified array - a Code step with JSON.parse() fixes stubborn cases.
Why does my webhook return 415 unsupported media type for text/plain from Node.js?
A 415 means the receiving endpoint rejected your Content-Type header. Your Node.js code is sending the body as text/plain (the default when you pass a plain string to fetch/axios without headers), but the endpoint - Zapier raw webhooks included - expects application/json. Fix: set the header explicitly and stringify the body: fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }). With axios, pass a JavaScript object (it sets the JSON header automatically) rather than a pre-stringified string. If you genuinely must send text, switch the receiving end to a "Catch Raw Hook" style trigger that accepts any content type.
How do I fix Supabase RLS infinite recursion when selecting from auth.users?
The error "infinite recursion detected in policy" happens when a row-level security policy on a table runs a subquery against the same table (or a view that re-triggers the policy) - commonly a profiles or members policy that checks membership by selecting from itself. Fix: break the cycle. Either (1) wrap the lookup in a SECURITY DEFINER SQL function, which bypasses RLS for that internal query, and call that function in the policy; (2) use auth.uid() and JWT claims (auth.jwt()) directly in the policy instead of re-querying the table; or (3) move role/team data into a separate table whose policies don't reference back. Never query auth.users directly from client-side policies - mirror what you need into a public profiles table maintained by a trigger.
Why does my Stripe checkout or billing portal loop back to login with a custom domain and Auth0?
The loop is a session-cookie mismatch: Stripe redirects the customer back to your return_url, but that URL is on a different (sub)domain than the one holding the Auth0 session cookie, so your app treats the user as logged out, bounces to Auth0, which bounces back, and so on. Fixes: (1) make the Stripe success/cancel/return URLs match the exact origin the user logged in on - same scheme, same subdomain (www vs apex is the classic culprit); (2) set Auth0 cookies at the parent-domain level (cookie domain .yourdomain.com) so they survive subdomain hops; (3) add the return URL to Auth0's Allowed Web Origins and enable silent re-auth so a bounced session recovers without showing a login wall; (4) if using Stripe's custom checkout domain, confirm the billing portal return URL was also updated - it's configured separately.
Why is PostHog autocapture breaking my shadcn/Radix dropdown menu listeners?
PostHog's autocapture attaches document-level capture listeners; shadcn/ui dropdowns (built on Radix primitives) rely on pointer-event sequencing and portal elements, and the capture-phase handler can interfere - the menu closes instantly or items don't fire onSelect. Fixes that work: (1) upgrade posthog-js - multiple releases addressed event interference; (2) add the ph-no-capture class to the dropdown trigger and content so autocapture skips those elements; (3) if it persists, set autocapture: false at init and instrument the events you actually need via posthog.capture() - manual events are more reliable for analytics anyway; (4) confirm the issue by commenting out PostHog init: if the dropdown works, you've isolated it. Don't ship a broken nav to keep heatmaps - capture less, deliberately.
Why does Stripe Tax automated calculation fail for a province like Ontario with custom metadata?
Stripe Tax needs a fully resolvable customer address and an active tax registration for the jurisdiction. If you pass the province in a custom metadata field instead of the structured customer.address.state, Stripe never sees it; and without a Canadian tax registration it refuses to calculate for Ontario. Fix: populate the structured address object using the two-letter subdivision code (ON for Ontario) rather than metadata, add your Canada/Ontario registrations under Stripe Tax settings, and assign a tax code to the product. Verify with a test customer at an Ontario postal code before going live.
Why does my Vanta compliance check fail on a Bubble static S3 asset?
Vanta flags Bubble-hosted static assets when the underlying S3 object is missing a response header the check expects - typically a cache-control or security header. Because Bubble manages that S3 bucket, you can't set the headers directly. Fix: front the assets with a CDN or proxy (Cloudflare is common) that injects the required headers on the way out, or move the specific flagged static assets to a bucket you control where you can set object metadata. Re-run the Vanta test after the header is present at the URL it checks.
Related reading: Automation Rescue • HireWilliam vs Zapier • HireWilliam vs Make • Run a $10k MRR Startup on Free Tiers • AI Agents vs Workflow Automation