Skip to content

Workflow Diagnostics

This content is not available in your language yet.

workflowDiagnostics reads one workflow graph and lists things that will probably not do what you meant when the workflow runs: a router with nowhere to go when nothing matches, a branch that can never be taken, an extraction option the node will ignore. Each finding names the node and says what to do.

Diagnostics never block saving or publishing. Publishing has its own checks (What publishing checks); a few diagnostics repeat those as INFO, so you see the problem in a draft before you press Publish.

query WorkflowDiagnostics($id: Int!, $published: Boolean) {
workflowDiagnostics(id: $id, published: $published) {
severity
code
nodeId
edgeId
message
}
}
  • By default it checks the draft graph, the one updateWorkflow writes.
  • With published: true it checks the published snapshot that real sessions run. A workflow that was never published answers NOT_FOUND for this.
  • An empty list means nothing was found.

Full reference: workflowDiagnostics.

  • WARNING: the graph will probably not do what you meant at run time (a run that fails, a branch that silently stops, a value that is never what you expect).
  • INFO: a hint, or a repeat of a publish rule.

code is a stable identifier: switch on it in your own tooling, not on message, whose wording may change. New codes may be added later, so treat an unknown code as a generic finding.

CodeSeverityWhat it meansWhat to do
FAN_IN_WITHOUT_JOINWARNINGA node has two or more incoming edges from paths that can both run in the same turn (they split at a node that follows every outgoing edge). The engine has no join: the node runs once, when the first path reaches it, and a later arrival is skipped (VISIT_LIMIT_REACHED in the trace).Put the steps in one sequence, or make sure the node does not depend on values the other path writes.
UNREACHABLE_NODEWARNINGNo path from the start node reaches this node (edges and router defaultTarget are followed). Publish refuses such a graph.Connect the node, or delete it.
ROUTER_NO_DEFAULTWARNINGA router has no defaultTarget (and no circuit breaker that trips on the first miss). A value that matches no edge label fails the run, and the user gets the generic error reply.Set defaultTarget to a fallback node.
ROUTER_DEFAULT_TARGET_MISSING_NODEWARNINGA router’s defaultTarget names a node id that does not exist. A miss routes there and the branch silently stops.Point defaultTarget at an existing node id.
ROUTER_CIRCUIT_BREAKER_TARGET_MISSING_NODEWARNINGA router’s circuitBreaker.target names a node id that does not exist. When the breaker trips the branch silently stops.Point circuitBreaker.target at an existing node id.
ROUTER_DOTTED_VARIABLE_OF_TEXTWARNINGA router’s variable is a dotted path whose first part is the output of an LLM node without outputSchema. That output is text, and a path cannot be read from text, so the router never matches.Write the value to its own variable with a separate step (for example an LLM node with matchAnyOf) and route on that variable.
ROUTER_DUPLICATE_OR_UNLABELLED_EDGEWARNINGA router edge has no label, or repeats a label another of its edges already has. A router follows only the first edge whose label equals the value, so this edge is never taken.Give each router edge its own label, or delete the extra edge.
CONDITION_MISSING_BRANCHWARNINGA condition lacks an edge labelled ‘true’ or ‘false’. When the expression gives the missing result, the run fails.Add edges labelled exactly ‘true’ and ‘false’.
LLM_HELPER_IGNORED_WHEN_STREAMINGWARNINGAn LLM node sets extractFirstInteger, matchAnyOf or extractAlphanumericId but not streamOutput: false. Streaming is the default, and the streaming executor ignores these helpers, so the raw reply is stored.Set streamOutput: false on the node.
FAILURE_WITHOUT_SUCCESSINFOA node that supports failure routing (tool, action, webhook, llm, sub_agent, set_variable, pdf_export) has a ‘failure’ edge but no ‘success’ edge. Publish refuses this.Add an edge labelled ‘success’.
SUCCESS_WITHOUT_FAILUREINFOA node that supports failure routing (tool, action, webhook, llm, sub_agent, set_variable, pdf_export) has a ‘success’ edge but no ‘failure’ edge. If the node fails, the whole run fails.Add an edge labelled ‘failure’ to handle a failed step, if the conversation should continue.
FAILURE_EDGE_ON_UNROUTABLE_NODEINFOA response, end_session, condition or switch node has an edge labelled ‘success’ or ‘failure’. Only tool, action, webhook, llm, sub_agent, set_variable and pdf_export nodes route on failure; on this node the label has no failure meaning (a response or end_session follows every edge, a condition or switch follows only its own labels).Move the failure handling to the step that can fail, or remove the label.
DEAD_END_NON_TERMINALINFOA node other than response or end_session has no outgoing edge. The turn ends there; if nothing spoke earlier on that path, the user gets no reply.Connect the node to the next step, usually a response node.
EXPLICIT_MAXVISITS_NO_ESCAPEINFOA node sets maxVisits but has no ‘loop_exhausted’ edge. If the graph reaches it once more than maxVisits allows, the run fails.Add an edge labelled ‘loop_exhausted’ to say where to go when the loop runs out.
RETRY_NOT_IDEMPOTENTWARNINGA tool, action or webhook node lists TIMEOUT or UPSTREAM_ERROR in retry.on, but its request is not known to be idempotent (no retry.idempotent: true; for a webhook, the method is not GET and there is no Idempotency-Key header). After a timeout or a 5xx the service may already have acted, so those classes are never retried on this node. A connection that was never made, and a 429, are still retried. In production since 2026-10-24.If sending the request twice is harmless, set retry.idempotent to true or send an Idempotency-Key header the service honours. Otherwise remove TIMEOUT and UPSTREAM_ERROR from retry.on. See Retrying a failed call.
LLM_STRUCTURED_WITHOUT_FAILURE_EDGEINFOAn LLM node has an outputSchema (structured output) and no ‘failure’ edge. When the reply still does not match the schema after the one repair call, or is cut off at the token limit, the node fails, and without a ‘failure’ edge the whole run fails and the user gets the generic error reply. In production since 2026-10-27.
MOCK_RESPONSE_SETINFOA node has a mockResponse. Only a mock-mode test (a draft test or regression replay in mock mode) uses it, returning it instead of calling out; a live run ignores it. A draftMocks entry for the same node wins over it. Available in production since 2026-10-29.Nothing to fix. Keep it close to what the real call returns, or remove it when it no longer matches. See Testing a Workflow Safely.
PLAINTEXT_SECRET_IN_CONFIGWARNINGA node’s configuration looks like it holds a literal credential. Available in production since 2026-10-29.
  • FAN_IN_WITHOUT_JOIN. The workflow engine has no “wait for both branches” step. When two paths that both run in the same turn lead into one node, that node runs once, as soon as the first path reaches it; the later arrival is skipped and shows in the run trace as VISIT_LIMIT_REACHED (see Loop re-entry). Paths that split at a condition, router, approval or await_reply node are exclusive (only one runs), so a shared step after them is not flagged.
  • ROUTER_NO_DEFAULT. Since 2026-10-10 a router whose value matches no edge label and that has nowhere else to go fails the run. A circuit breaker only takes over after its configured number of misses in a row, so it covers the first miss only when maxConsecutiveDefaultRoutes is 1.
  • ROUTER_DOTTED_VARIABLE_OF_TEXT. An llm node without outputSchema stores its answer as text. A router variable such as analysis.severity cannot read inside text even when it looks like JSON. Give the llm node an outputSchema (it then stores an object, and the warning is not raised; in production since 2026-10-27), or write the value to its own variable with a separate step, for example an llm node with matchAnyOf, and route on that.
  • LLM_STRUCTURED_WITHOUT_FAILURE_EDGE. A structured llm node makes at most one repair call; a reply that is still invalid fails the node. Add a success and a failure edge, and on the failure branch ask the user again with {{_lastNodeErrorPublic}} (the code is INVALID_DATA). See One repair call, then the failure edge.
  • FAILURE_WITHOUT_SUCCESS, SUCCESS_WITHOUT_FAILURE and FAILURE_EDGE_ON_UNROUTABLE_NODE. Since 2026-10-15 failure routing works on six node types, not only tool and action, and the first two codes cover all six. For a sub_agent, SUCCESS_WITHOUT_FAILURE adds that the node reports a failed target in its resultVar and only takes failure for a configuration error. FAILURE_EDGE_ON_UNROUTABLE_NODE is new: it flags a success or failure label on a node type that cannot route its own failure. A router is not flagged, because success and failure are ordinary route values there. See Failure routing.
  • LLM_HELPER_IGNORED_WHEN_STREAMING. extractFirstInteger, matchAnyOf and extractAlphanumericId only work on a node with streamOutput: false; an llm node streams by default (details).
  • PLAINTEXT_SECRET_IN_CONFIG. A heuristic, never blocking, that looks only at configuration that is sent to a third party: a webhook node’s url, headers and bodyTemplate, and a tool or action node’s argsTemplate. It flags a secret-named header (such as Authorization, X-Api-Key, Cookie) with a literal value, a URL query parameter such as token, key, secret, password or api_key with a literal value, user:password@ inside a URL, a secret-named JSON key with a literal value, Bearer or Basic literals, and a few well-known key shapes. It skips values that contain {{ (a template), values under 8 characters and names that are not secret-like. The message names the node and the field and never repeats the value. Where to move the secret: for an action node, the custom action’s bearerToken (with credentialSecretHeaderName if the header is not Authorization), not extraHeaders; for a tool node, the credential on the MCP connector; a webhook node has no secret store, so move the call to an action node.