Skip to content

fix: classify wired nodes without editor x/y as regular nodes, not config nodes - #29

Merged
tbrandenburg merged 1 commit into
mainfrom
fix/issue-28-flows-settled-race
Sep 6, 2026
Merged

fix: classify wired nodes without editor x/y as regular nodes, not config nodes#29
tbrandenburg merged 1 commit into
mainfrom
fix/issue-28-flows-settled-race

Conversation

@tbrandenburg

Copy link
Copy Markdown
Owner

Issues fixed

Closes #28

Summary

Node-RED's flow parser (@node-red/runtime/lib/flows/util.js) classifies any node lacking both x and y properties as a global config node, regardless of its actual type. Hand-authored --flow-json flows (this tool's core use case — see the README's own agent example) commonly omit those editor-only coordinates. A wired node misclassified as a config node undergoes Flow.js's config-node circular-dependency scan instead of normal instantiation, which throws Circular config node dependency detected the moment one of its own property values equals another node's id — including its own id, e.g. when name equals id (exactly what the README's agent example does). That aborts the whole flow's instantiation, surfacing as target ... is not instantiated, and made the unrelated waitForFlowsSettled() stop/safe race look like the root cause in the original bug report.

Root cause confirmed by:

  • reading @node-red/runtime's parser (n.hasOwnProperty('x') && n.hasOwnProperty('y') gate) and Flow.js's circular-dependency scan
  • reproducing 100% with core node types only (no external package needed)
  • reproducing against the real @tbrandenburg/node-red-agents package from the issue, in a scratch dir outside the repo

Fix

  • withDeployCoordinates() in src/run-envelope.js assigns synthetic x/y to every node that's unambiguously wired (has a wires array, or is a link out node) and lacks coordinates, before building the in-memory flow storage module. Genuine config nodes (which have neither) are left untouched.
  • Also hardened waitForFlowsSettled() per the issue's own suggested refinement: a stop/safe runtime-state event now defers resolution by one macrotask so an already-scheduled flows:started for the same deploy attempt wins instead — defensive insurance, even though analysis of the installed @node-red/runtime shows stop/safe and flows:started are mutually exclusive outcomes of a single deploy attempt today.

Validation

E2E / real-world coverage

  • New integration test (test/integration/run-envelope.integration.test.js) reproduces the exact failure end-to-end against the real embedded Node-RED runtime using a minimal self-named link in -> link out flow (no mocks), and asserts a successful deploy + call.
  • Additionally manually verified (outside the repo, cleaned up after) against the real @tbrandenburg/node-red-agents@0.4.1 package with the README's own documented agent flow shape: before the fix — Error: Circular config node dependency detected: ask; after the fix — deploy succeeds and the agent node's real execution path runs.

Risks / follow-ups

…nfig nodes

Node-RED's flow parser treats any node lacking both x and y properties as
a global config node, regardless of its type. Hand-authored --flow-json
flows (this tool's core use case, per the README's own agent example)
commonly omit those editor-only coordinates, so a wired node ends up
misclassified as a config node and undergoes Flow.js's circular config
node dependency scan instead of normal instantiation. That scan throws
"Circular config node dependency detected" the moment one of the node's
own property values equals another node's id -- including its own id,
e.g. when name equals id (exactly the README's agent example). This
aborts the whole flow's instantiation, which previously surfaced as
'target ... is not instantiated' and made the unrelated
waitForFlowsSettled() stop/safe race look like the root cause.

Assign synthetic x/y coordinates to every unambiguously wired node (has a
wires array, or is a link out node) before deploying an in-memory flow,
leaving genuine config nodes untouched.

Also add a one-tick debounce in waitForFlowsSettled() before resolving on
a stop/safe runtime-state event, so a flows:started already scheduled for
the same deploy attempt wins the race instead -- defensive hardening per
the issue's own suggested refinement, even though current analysis shows
stop/safe and flows:started are mutually exclusive outcomes in the
installed @node-red/runtime version.

Fixes #28
@tbrandenburg
tbrandenburg merged commit 5d359b7 into main Sep 6, 2026
7 checks passed
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.

v0.2.14 regression: flows using the agent node (node-red-agents) now deterministically fail with 'Circular config node dependency' / 'not instantiated'

1 participant