Skip to content

fix(n8n): three real bugs surfaced dogfooding the n8n parser on 308 real workflows#56

Merged
hatyibei merged 3 commits into
mainfrom
fix/n8n-ai-edge-reachability
Jun 19, 2026
Merged

fix(n8n): three real bugs surfaced dogfooding the n8n parser on 308 real workflows#56
hatyibei merged 3 commits into
mainfrom
fix/n8n-ai-edge-reachability

Conversation

@hatyibei

@hatyibei hatyibei commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Dogfooding the n8n parser against 308 real workflows (enescingoz/awesome-n8n-templates) surfaced three pre-existing correctness bugs in the langchain-AI path. All reproduce on npx shingan-lint demo-grade inputs and all undermine "run shingan analyze on your own graph" as a credible CTA — so they're correctness, not features.

unreachable_node across the three commits: 973 → 444 → 198 (−80%).

Bug 1 — systematic unreachable_node FP on AI sub-resources (4cb6eca)

n8n attaches an AI Agent's sub-resources (model / tool / memory / output parser) via ai_* connections whose source is the sub-resource, so the parser emits sub-resource → Agent edges. The trigger reaches the Agent via main, but nothing flows into the sub-resource → every model/tool/memory node is flagged unreachable.

  • Fix: treat ai_* edges as undirected for reachability only. A naive bidirectional edge in the parser would instead turn the FP into a cycle_detection 2-cycle FP, so the fix lives in the rule.
  • Impact: 973 → 444, FP reduced in 171/308 workflows.

Bug 2 — cycle_detection non-determinism (6d85a1d)

The same workflow reported a different node of a multi-node cycle each run. Two map-iteration sources fed the DFS visit order, which decides the back-edge target.

  • Measured: total flipped 62 ↔ 63; 4 workflows reported a different Critical node each run (e.g. AI Youtube Trend Finderif_longer_than_3_ / loop_over_items1 / find_video_data1).
  • Fix: sort both the isolated-cycle start order and the per-node outgoing walk inside the detector (parser edge order untouched). After: 6/6 runs == 62, 0 flaky files.
  • Why it matters: a static analyzer that flags a different node each run can't go in CI.

Bug 3 — chatTrigger mis-typed as LLM, orphaning chat workflows (cfcf181)

@n8n/n8n-nodes-langchain.chatTrigger / manualChatTrigger contain "n8n-nodes-langchain", so the broad langchain → LLM rule matched them before the trigger rule. They lost category="trigger", were never collected as triggers, and were never picked as the entry — so in an AI chat workflow (where the chatTrigger is usually the only entry) the whole downstream graph was unreachable.

  • Fix: classify triggers/webhooks at the top of mapN8nNodeType, before the langchain rule (old trailing check removed as dead code).
  • Impact: 444 → 198.

Tests

  • AISubResourceReachableViaAgent / …UnreachableIfAgentIsolated
  • DeterministicIsolatedCycle (50 repeat analyses → stable, sorted-first node)
  • ChatTriggerIsEntry (chatTrigger is a trigger Tool AND the chosen entry)
  • go test ./domain/rules/... ./infrastructure/parser/... green

Not addressed here (follow-ups)

🤖 Generated with Claude Code

hatyibei and others added 2 commits June 18, 2026 16:33
…ources

n8n attaches an AI Agent's sub-resources (model / tool / memory / output
parser) via ai_* connections whose SOURCE is the sub-resource and whose
target is the Agent, so the parser emits sub-resource -> Agent edges. The
trigger reaches the Agent through the main chain, but nothing flows INTO a
sub-resource, so reachability flagged every model/tool/memory node as
unreachable_node. On real langchain-heavy n8n workflows this fired on nearly
every AI node (3-node trigger->Agent + ChatModel repro emits a 100%-
confidence FP).

Fix: treat ai_* edges as UNDIRECTED for reachability only -- a sub-resource
of a reachable Agent is reachable (the Agent invokes it at runtime). The
directed graph that cycle_detection consumes is left untouched: a naive
bidirectional edge in the PARSER instead converts the unreachable FP into a
cycle_detection FP (Agent<->sub-resource 2-cycle), so the fix lives in the
reachability rule, not the parser.

Tests: AISubResourceReachableViaAgent (sub-resources of a reachable Agent ->
0 findings) + AISubResourceUnreachableIfAgentIsolated (the undirected hop
does not leak: an isolated Agent and its sub-resource stay unreachable).
go test ./domain/rules/... ./infrastructure/parser/... green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cycle_detection reported a DIFFERENT node of a multi-node cycle on each run
of the same workflow. Two map-iteration sources fed the DFS: the isolated-
cycle start loop (`for id := range graph.Nodes`) and the per-node outgoing
walk (OutgoingEdges follows the parser's edge-construction order, which for
the n8n parser is itself Go map iteration). The DFS visit order decides which
node is seen as the back-edge target, so the reported Critical node was
non-deterministic.

Measured on 308 real n8n workflows (enescingoz/awesome-n8n-templates): total
cycle_detection findings flipped between 62 and 63 across runs, and 4 specific
workflows reported a different node each run (e.g. "AI Youtube Trend Finder"
cycled through if_longer_than_3_ / loop_over_items1 / find_video_data1). After
sorting both the isolated-start order and the outgoing-edge order inside the
detector: 6/6 runs == 62, 0 flaky files. The fix is contained in the rule (the
parser's edge order is left as-is), so no fixtures change.

This matters for the determinism guarantee the rule advertises: a static
analyzer that flags a different node each run cannot be put in CI.

Test: DeterministicIsolatedCycle (50 repeat analyses of an isolated 3-node
cycle must report the same, sorted-first node). go test ./domain/rules/...
./infrastructure/parser/... green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ecc-tools

ecc-tools Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

…ion)

@n8n/n8n-nodes-langchain.chatTrigger and manualChatTrigger contain the
substring "n8n-nodes-langchain", so the broad `langchain → LLM` rule in
mapN8nNodeType matched them BEFORE the trigger rule. They lost their
Config["category"]="trigger", were never collected by allTriggerNodeIDs,
and so were never picked as the entry node. In an AI chat workflow the
chatTrigger is usually the ONLY entry, so the entire downstream graph
(Agent + model + memory + tools) was reported as unreachable_node.

Fix: classify triggers/webhooks at the TOP of mapN8nNodeType, before the
langchain→LLM rule (the old trailing trigger check is now dead code and is
removed). Anything whose lowercased type contains "trigger"/"webhook" is a
trigger; no real langchain LLM node contains those substrings.

Measured on 308 real n8n workflows, unreachable_node findings:
  973 (pre-fixes) -> 444 (ai_* undirected) -> 198 (this fix)  = -80% total.

Test: ChatTriggerIsEntry (chatTrigger is a trigger Tool AND the chosen
entry node). go test ./domain/rules/... ./infrastructure/parser/... green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ecc-tools

ecc-tools Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@hatyibei hatyibei changed the title fix(n8n): two real bugs surfaced dogfooding the n8n parser on 308 real workflows fix(n8n): three real bugs surfaced dogfooding the n8n parser on 308 real workflows Jun 19, 2026
@hatyibei hatyibei merged commit 55256b0 into main Jun 19, 2026
11 checks passed
@hatyibei hatyibei deleted the fix/n8n-ai-edge-reachability branch June 19, 2026 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant