cleancontext.blog
post 03 · n8nLer em português

The same key, hand-written 15 times, broke an agent

Found in staging: a lock that was supposed to disappear once the conversation moved on never did, because two different formulas built the same key.

summary

In staging, my agent stopped responding with no visible error. The cause: a timeout lock that was supposed to be cleared once the conversation moved on, and wasn’t, because the node that creates the key and the node that deletes it use two different formulas to build the same identifier. I went looking for how many other places in the workflow built that same key family by hand: more than 15 different nodes, each with its own interpolation.

In staging, one of my workflows stopped responding mid-test. No visible error in n8n, no exception in the logs — the agent just went silent. The cause: a timeout lock that was supposed to be removed once the conversation moved on, and wasn’t.

I went after the key that controls that lock: Helena_{{ ... }}_timeout. I found the node that creates the lock (set, with a 24h TTL) and the node that was supposed to delete it (delete). Two different constructions of the same key:

  • create:Helena_{{ contact_inbox.source_id }}_timeout
  • delete:Helena_{{ message.phone.replace('+','') }}_timeout

When source_id and the phone number without + aren’t the same value, the delete never matches the key the set created. The lock just sits there until the TTL expires on its own. I found the problem by searching for _timeout inside the JSON — nobody had compared those two strings side by side before.

I went to check how many other places in the workflow built that same key family (Helena_{{ phone }}, used as the session identifier for memory, message buffer, and history) and it wasn’t 2. It was more than 15 different nodes, each with its own interpolation, each depending on me remembering to keep the same formula across all of them.

Why this isn’t a lapse in attention

The workflow had prompt injection guardrails, CNPJ validation, handoff reason classification — plenty of care went into what the AI does. But the key that ties all of that to the same conversation was loose text, copied and pasted into every node that needed it. There was no single place defining “this customer’s session is X” — there were 15 places that agreed, until the day one of them didn’t anymore.

It’s the same problem as duplicated credentials that I already solved with Credential Align, just with a variable instead of a credential: a value that should have a single source of truth ends up hardcoded in every node that needs it, and drifting is expensive — in this case, a silent agent. Found in staging, before it reached the customer, but only because I happened to be testing that specific part of the flow at that moment. In a less-observed part of the flow, the same bug would have slipped through.

What the tool does

I built the Variable Normalizer to catch this pattern before it becomes an incident, right in the browser.

  • 1.Paste the exported workflow JSON.
  • 2.The tool scans every node looking for the same value (string, number, n8n expression) repeated across different parameters — not just identical values, but also constructions that should be identical and aren’t (e.g. two expressions that build the same logical key from different data sources).
  • 3.It shows each repetition group: the value, how many nodes use it, and — when the interpolation drifts between occurrences of the “same” value — flags it as suspicious instead of staying quiet.
  • 4.It suggests where to centralize: a Set node right after the flow’s entry point, holding the value once, with the rest of the workflow reading from there.

None of this leaves the browser. Parsing and scanning both run on the client.

I ran the tool against the very workflow that produced this post: 36 groups of identical values repeated across 2+ nodes, and 4 formula drifts — with, in first place, exactly the source_id vs. phone pair from the incident.

What actually matters

Repeating a value across two nodes is harmless until it stops being the same value. The tool doesn’t stop anyone from writing the same formula twice — it stops the second copy from drifting away from the first without anyone noticing.

Questions

Because there's no single place that defines the value — there are N places that need to agree with each other. Every node that rebuilds the key is a chance to drift: if one of them reads from a slightly different data source, the key it produces stops matching the others, and everything that depends on it (memory, buffer, lock) silently stops working.
Because manual testing only covers what someone happens to observe at that moment. If the drift lives in a timeout lock that only triggers under certain conditions, it can stay invisible through every case a developer tests by chance — and only show up when that specific scenario occurs later.
Besides flagging the same hardcoded value across nodes, the tool flags when two expressions should build the same logical value (the same key, the same identifier) but pull from different data sources — a subtler pattern than plain repetition, and a more dangerous one, because it only breaks once the sources actually diverge. That's how it found, in this very workflow, exactly the source_id vs. phone drift from the incident.
No. It points to where to centralize the value (typically a Set node right after the flow's entry point) and lists the repetition and drift groups — deciding the final source of truth and rewriting the nodes is up to whoever knows the flow.

Comments

  • No comments yet.