Process automation
Process automation: when a renamed variable sent 412 DMs
A junior engineer at a Ghent recruitment agency renamed one variable inside a Make.com scenario. Nine days later, 412 LinkedIn candidates had received the wrong vacancy. Here is exactly what failed.

The first message came from a client. Tuesday morning, 09:14, a Slack ping to the founder of a 19-person recruitment agency in Ghent whose sourcing automation had been running, untouched, for fourteen months: "Why did three of my engineers get a DM from you about a Java role? We hired you for the Go position."
Within an hour, two more clients said something similar. By lunch, the agency had pieced together what had been happening for the previous nine days. Their Make.com scenario, the one that sourced candidates from LinkedIn Sales Navigator and sent the first-touch DM, had been quietly DM'ing 412 people with the title of the wrong vacancy. Every message was polite, on-brand, and personalised. The only thing wrong was the job.
This post is the post-mortem. We have changed the agency name and a few identifying numbers. Everything that matters about the failure is real.
The scenario the team thought they had
The original Make.com scenario was about as simple as a sourcing pipeline can get. A trigger fired when a recruiter dropped a candidate into a Notion roster tagged "ready-to-contact". A lookup module pulled the matched vacancy from a second Notion table. A "Set variable" step stored the vacancy title for use later in the run. A LinkedIn module then sent the DM, with a soft personalisation line in front of the title.
This scenario had been running, untouched, for fourteen months. Sourcing volume on a busy week peaked at around 60 candidates a day. The team trusted it the way you trust a kettle: you do not check that it has boiled.
The rename
Two weeks before the incident, a junior automation engineer was tidying up. The scenario had grown a sibling: a second variant for one client who wanted a different opening line. Both variants used a variable called vacancyTitle. The junior, sensibly trying to make the difference visible at a glance, renamed the variable in the second variant to vacancyTitle_clientX.
That part was fine. While he was tidying, he also renamed the live Set Variable step in the original scenario from vacancyTitle to vacancyTitle_main, thinking it was tidier. He believed he had updated every place the variable was read. He had missed one. The LinkedIn module's message template, six modules later, still referenced {{vacancyTitle}}.
Now we get to the ghost. Months earlier, somebody had added a Set Variable step at the very top of the scenario as a debug fallback, hard-coding vacancyTitle to "Senior Java Developer" so the team could test message rendering without a live record. That step had never been removed.
Before the rename, the live writer further down the scenario was overwriting the debug value on every run, so the ghost was harmless. After the rename, the ghost was the only thing writing to vacancyTitle. The LinkedIn template happily read it. Every candidate, regardless of role, regardless of client, regardless of which scenario was sending the DM, got "Senior Java Developer".
Why nobody saw it for nine days
Three things hid the failure.
First, the scenario reported green. Make's run history shows execution status per run, and every run completed without an error. The LinkedIn DM module returned a successful response. The candidate received the message. The operation count ticked over. From the dashboard, the automation was working exactly as it always had.
Second, the reply loop wasn't on the same scenario. Replies came back into the LinkedIn inbox, where a different team member triaged them. When a few candidates replied "I think this is the wrong role?", those messages were treated as one-off mismatches and the candidates were politely re-routed. Nobody connected the dots across days.
Third, and this is the part that turned a small bug into a nine-day bug: Make's run history search is scoped to a single scenario at a time. You cannot grep across all your scenarios and folders for the string "Senior Java Developer" and ask which runs ever contained that string in their output. If you have ever spent five minutes inside the Make help docs, you know that run history is one execution, one scenario, one screen at a time. For an audit question like "which of our scenarios ever sent the string X to LinkedIn", you open each scenario and search each run individually.
None of these three failure modes is unique to Make. The same combination shows up on every low-code platform we have audited: silent default behaviour on missing or stale values, per-resource search instead of cross-corpus search, and a separation between the system that sends and the system that listens for replies. Each one is reasonable in isolation. Together, they form a wall behind which silent failures live for a long time.
Blast radius
By the time we sat down with the founder on day ten, the blast radius was clear. 412 candidates had received the wrong vacancy title in their first-touch DM. 37 had replied: most confused, a handful annoyed. Two had explicitly asked to be removed from any future contact. One had screenshotted the DM and posted it to LinkedIn with the caption "this is why I don't trust recruiters". The post had several thousand reactions by lunch and most of the comments were people sharing their own version of the same story.
The cleanup
The agency's first instinct was to apologise to all 412. That was right, and they did it within 48 hours, with a short personal note from the founder per candidate. The second instinct was harder. They wanted to know which candidate had received which intended title, so the follow-up could acknowledge the specific job, not just "the wrong one".
This is where Make's lack of cross-scenario search hurt. To reconstruct the per-candidate audit trail, we pulled every run's bundle from the affected nine-day window through the Make API, parsed the input bundles, and rebuilt the mapping between candidate ID and intended-versus-sent vacancy. Roughly:
# Pull execution bundles for one scenario across a date window.
# There is no cross-scenario query; you do this per scenario.
import os, time, requests
TOKEN = os.environ["MAKE_API_TOKEN"]
ZONE = "eu2.make.com" # use your own Make zone
def list_runs(scenario_id, start_iso, end_iso, page_size=100):
url = f"https://{ZONE}/api/v2/scenarios/{scenario_id}/executions"
offset = 0
while True:
params = {"from": start_iso, "to": end_iso,
"pg[limit]": page_size, "pg[offset]": offset}
r = requests.get(url, params=params,
headers={"Authorization": f"Token {TOKEN}"}).json()
items = r.get("executions", [])
if not items:
return
for ex in items:
yield ex
offset += page_size
time.sleep(0.3) # be kind to the API
def fetch_bundle(scenario_id, execution_id):
url = (f"https://{ZONE}/api/v2/scenarios/"
f"{scenario_id}/executions/{execution_id}")
r = requests.get(url, headers={"Authorization": f"Token {TOKEN}"})
r.raise_for_status()
return r.json()
From the bundles, we joined on the LinkedIn module's output, extracted the rendered message body, and looked for the literal string "Senior Java Developer" where it did not match the candidate's actual vacancy ID. That join produced the 412.
The fix
The patch itself took twelve minutes. We deleted the orphaned debug Set Variable at the top, restored a single canonical name, and added a guard step that raised an explicit error if the resolved vacancy title was missing, empty, or did not match a vacancy ID in the candidate's record. That last check should have been there from day one. If the message body references a vacancy, the scenario should refuse to send if the vacancy can't be confirmed.
The harder work was structural.
Naming hygiene
Make's variable system is forgiving in the wrong direction. It will not warn you that you have an orphan variable, a stale write, or a name collision in the same folder. We adopted a convention: every variable used in a customer-facing message follows the pattern scenarioName_purpose, has exactly one writer, has every reader documented in the scenario notes, and gets reviewed any time the scenario is edited. Boring rules pay off when somebody new joins, and they pay off most on the day the person who built the thing is on a flight.
Single source of truth for the message body
The original scenario assembled the DM text from a template embedded inside the LinkedIn module's UI. A copy change meant opening Make. We moved the message body to a row in the same Notion table that stored the vacancy. The scenario now reads {{vacancy.message_body}} directly. Message body and vacancy title share a row, so they cannot drift.
An off-platform canary
The thing that finally caught the bug was a paying client. We did not want that to be the detection mechanism. We added a small canary scenario: every 30 minutes it pushes a fake candidate through the sourcing pipeline, with the DM step replaced by a webhook to a server we control. The server compares the rendered message body against the expected output for that vacancy. If the diff is non-empty, it pages.
This is a familiar pattern for anybody who has run a production service. For most small teams, Make.com or n8n or Zapier is their production service. It deserves the same observability.
If your no-code platform cannot grep across folders and scenarios for a string that was sent to a customer, you do not have an audit trail. You have a story your team is telling itself.
The broader pattern
The week we wrote this draft, the front page of Hacker News had several variants of the same conversation: an AI agent that scribbled over a Fedora install and a few other systems, an argument about guardrails on commercial agent platforms, and the rise of Burr from DAGWorks as a framework for building agents you can trace through. The shape of all those threads is the same one our recruitment agency had on day ten. The moment automation is doing real work on real people, "the run came back green" is not enough.
The argument the agent-tracing community keeps making is the right one: every external action your automation takes should be reconstructible after the fact, ideally from a single query. A no-code scenario that sends 60 DMs a day is, structurally, an agent. It takes inputs, makes decisions, produces effects in the world. If you cannot ask "what did it do and why?" in one query, you are flying blind.
What you need, at a minimum: an audit trail you can query across runs and scenarios, a canary that fires independent of the platform's own health check, and a refusal-to-send rule that is louder than the green tick. None of those are exciting to build. All of them would have shortened nine days to one.
The five-minute audit
If you run a Make.com, n8n, or Zapier setup that sends messages to humans, here is the audit you can do today. Pick three scenarios. For each, list every variable name used in a customer-facing message. Open the scenario, search for that name, and count the writes and the reads. If a name has more than one writer, or if any reader references a name nothing currently writes to, you have the same setup that hit Ghent.
When we rebuilt the process automation for the agency, the thing we ran into was that the Make scenario had grown four years of small edits with nobody owning the variable graph. We solved it by moving message templates into Notion and adding a canary that pages on diff, so the next forgotten debug step gets caught in 30 minutes instead of nine days.
Five minutes is enough to find out whether your own setup is one rename away from the same Tuesday morning.
Key takeaway
If your automation can't grep across runs for a string sent to a customer, your audit trail is a story your team is telling itself.
FAQ
What actually caused the wrong-DM bug?
A junior renamed the live Set Variable step. An old debug Set Variable at the top of the scenario, forgotten months earlier, kept supplying a stale title. The LinkedIn template silently used the stale value.
Why did the bug last nine days?
Every run reported green, replies went to a separate inbox triaged by a different person, and Make.com has no cross-scenario run-history search to grep for the wrong string.
How do you prevent this on Make.com?
Move templates to a single source of truth, enforce a one-writer variable convention, add an explicit refuse-to-send guard, and run an off-platform canary that diffs rendered output against expected.
Is this specific to Make.com?
No. Any low-code platform without cross-run search, explicit unset-variable errors, and a unified reply loop is vulnerable to the same silent-failure pattern. Make is just where this story happened.