Skip to main content

Validation Rules & Diagnostics

FlowForger checks a flow before Power Automate does. Most of these checks mirror a rule the cloud enforces when you save or activate a flow, so you see the problem in your editor instead of as a failed save. Others catch things the cloud accepts but that fail when the flow runs, or that the designer flags later.

There are two families of codes:

  • DSL codes (DSL001–DSL051) come from the DSL language service. They work on your .ff.ts source, so they point at the exact line.
  • Validator codes (ACTION_NAME_DUPLICATE, RUNAFTER_DEAD_STATUS, ...) come from @flowforger/validator. They work on the compiled IR or on Logic Apps JSON, so they also cover flows you did not write in the DSL (pulled flows, hand-edited clientdata.json).

Many rules exist in both families: the DSL code fires in the editor, and the validator code catches the same thing in JSON.

Severity​

SeverityMeaning
ErrorThe flow will not compile correctly, or Power Automate rejects it on save. flowforger validate exits with code 1.
WarningPower Automate saves the flow, but it fails or misbehaves at run time, or the designer flags it. Fix it unless you know why it is there.
InfoA hint. Nothing is wrong on its own.

Where you see them​

  • Editors. The web app's Flow Editor and the VS Code extension show DSL codes as squiggles while you type. Both hosts already show TypeScript's own syntax errors, so they do not repeat them as DSL051.
  • flowforger validate <flow.ff.ts> prints every DSL diagnostic as file:line:col SEVERITY [CODE] message. When there are no DSL errors, it then compiles the flow to IR and runs the validator too. It skips validator codes that have a DSL twin, so nothing is reported twice, and it prints info-level validator hints (except HTTP_RETRY).
  • flowforger validate <file.json> auto-detects IR or Logic Apps JSON and prints { ok, issues }.
  • Compiling. compile, run, push and the debuggers refuse a file with TypeScript syntax errors and list them with file:line:col (see DSL051). Other diagnostics do not stop a compile, so run validate before you push.
  • Saving in the web app. The Flow Editor runs the Logic Apps JSON checks before it saves a flow: errors block the save, and warnings ask you to confirm. Expression problems are also marked inline in the Logic Apps JSON tab.
flowforger validate flows/order-intake.ff.ts
flowforger validate clientdata.json

Flow structure and naming​

CodeSeverityWhat triggers itFix
DSL001ErrorThe class has no @Flow decorator.Add @Flow('Name') or @Flow({ name: 'Name' }).
DSL002ErrorNo method has a trigger decorator.Add one of @HttpTrigger, @ManualTrigger, @RecurrenceTrigger, @ConnectorTrigger.
DSL003ErrorNo method has @Action().Put the flow logic in one @Action() method.
DSL019ErrorMore than one @Action() method. Only the first is compiled; the others are silently ignored.Merge the logic into one method.
DSL023Error@Flow has an empty name.Give the flow a name.
DSL008ErrorTwo actions have the same name.Rename one. Names must be unique across the whole flow, including inside scopes, loops and switch cases.
ACTION_NAME_DUPLICATEErrorSame rule on IR/JSON. The comparison is case-insensitive (Get_items and get_items collide), as in the cloud's DuplicateActionName.Rename one.
DSL035ErrorAn @action value contains @{, a control character, or one of < > % & \ ? /. Power Automate rejects such a name on save. The name is the rest of the @action line, so a note written after the name on the same line becomes part of it.Put notes on their own line, or in a // comment above.
ACTION_NAME_INVALIDErrorSame rule on IR/JSON, for action and trigger names.Rename.
DSL039ErrorAn action name is longer than 80 characters.Shorten it.
ACTION_NAME_LENGTHErrorSame rule on IR/JSON, also for the trigger name.Shorten it.
DSL024Warning@type has a value other than scope, if, foreach, switch, until, dountil or case.Fix the value. Only @type scope is ever needed (see Annotations).
DSL027WarningThe JSON after @metadata, @retryPolicy, @trackedProperties or @runtimeConfig does not parse. The annotation is ignored.Fix the JSON (double-quoted keys, no trailing commas).
DSL026InfoA ctx.<name>() call that is not a known FlowContext method. It is passed through as the expression function <name>().Check for a typo.
IR_NAMEErrorIR without a flow name.Add one.
IR_WORKFLOW_IDErrorIR workflowId that is not a GUID string.Fix or remove it.
IR_TRIGGERErrorIR without exactly one trigger node (type: 'trigger' or type: 'recurrence').Keep one trigger.
IR_ACTIONSWarningThe flow has no actions.Add actions.
TRIGGER_COUNTWarningLogic Apps JSON with more than one trigger. The cloud saves it, but the designer and FlowForger work with one: only the first is shown, run locally and kept when converting to the DSL.Keep one trigger.
DEF_MISSING, DEF_TYPE, DEF_TRIGGER, DEF_TRIGGER_TYPE, DEF_TRIGGER_EMPTY, TRIGGER_INVALIDErrorLogic Apps JSON whose definition or triggers is missing or not an object, or a trigger that is not an object.Fix the JSON shape.
TRIGGER_TYPEWarningA Logic Apps trigger without type.Add it.
DESCRIPTION_LENGTHErrorAn action or trigger description in Logic Apps JSON is longer than 256 characters. Power Automate refuses it on save. The emitter never writes one: it stores a longer comment as a 256-character excerpt plus the full text in the action's metadata.Shorten it, or let the emitter handle it.

TypeScript the compiler cannot use​

CodeSeverityWhat triggers itFix
DSL051ErrorA TypeScript syntax error (a missing comma, an unterminated string). TypeScript recovers from these silently, so without this check the half-parsed code would compile into broken expressions. compile, run, push and the debuggers refuse the file.Fix the syntax.
DSL017ErrorA ctx action call without await. It is left out of the compiled flow.Add await.
DSL018ErrorA return statement in the @Action method. It has no effect: the following actions still run.Use await ctx.terminate(...) to end the run.
DSL020Warningthrow, try/catch, break or continue. They have no Logic Apps equivalent and are ignored. A break at the end of a switch case is harmless (cases never fall through).Use scopes with @runAfter for error handling (see Control Flow), and ctx.terminate() to stop.
DSL032Errorconst x = await ctx.<action>(...) (or let). The binding is dropped and x compiles to a broken @x reference.Call the action as a statement and read it with ctx.body('Name') / ctx.outputs('Name').
DSL025WarningA const bound to a literal, object or array, or to a ctx reference read such as ctx.variables('x'). A const never creates a Power Automate variable. Reads of a literal const compile to broken text (const n = 5 gives @5); reads of a reference read are inlined, which works.Use let for a value. A const bound to an expression (const name = ctx.triggerBody()?.['name']) is an alias and is inlined at every read.
DSL028ErrorA quoted spread in an array literal: ['...items', value]. The string is taken literally.Use items.push(value).
DSL029Warningx = [...x, value] or x = x.concat(value). This compiles to Set variable, not Append to array variable.Use x.push(value).

A constant declared above the class is not checked: const SITE = '...' outside the class compiles to the text @SITE. Put configuration in flow parameters and read it with ctx.parameters('Name').

Variables​

CodeSeverityWhat triggers itFix
DSL005ErrorA variable is used before it is declared.Declare it (with let) earlier, at the top level.
DSL007WarningA variable is declared but never used.Remove it.
DSL014Errorlet inside an if, loop, switch or try. Power Automate only allows Initialize variable at the top level.Declare the variable at the top of the @Action method and assign it inside the block.
VAR_INIT_NESTEDErrorSame rule on IR/JSON, including inside a scope.Move the initialization to the top level.
DSL030ErrorThe same variable name is declared twice.Declare it once and assign it afterwards.
VAR_INIT_DUPLICATEErrorSame rule on IR/JSON. Names are compared case-insensitively. Cloud error: "A variable must only be initialized once".Initialize it once.
VARIABLE_UNDEFINEDErrorAn expression reads variables('x'), but nothing initializes x. Cloud error: "The variable 'x' must be initialized before it can be used".Initialize the variable, or fix the name.
VARIABLE_UNINITIALIZEDErrorA Set, Increment, Decrement or Append action targets a variable that nothing initializes.Same.
VARIABLE_TYPEErrorA variable type other than String, Integer, Float, Boolean, Array or Object.Fix the type.
DSL042ErrorMore than 250 variables.Group related values into one object variable.
VARIABLE_COUNTErrorSame limit on IR/JSON.Same.
DSL047WarningA variable is set, appended to (.push()), or appended to as a string (+= on a string) inside a for...of loop that runs in parallel. In the cloud an Apply to each runs up to 20 iterations at once, while a local run takes one at a time, so the result can differ after you deploy. Increments and decrements are not reported: the cloud applies them atomically.Add @runtimeConfig {"concurrency":{"repetitions":1}} to the loop, or build the result with ctx.select() / ctx.filterArray().

Parameters and connection references​

CodeSeverityWhat triggers itFix
DSL015Warningctx.parameters('X') names a parameter that is not in ctx.flow.parameters. Names must match exactly, including case. The cloud saves the flow, but the expression has nothing to read.Use the exact key from the constructor.
EXPR_UNKNOWN_PARAMETERWarningSame rule on IR/JSON. $connections and $authentication always exist.Same.
DSL016ErrorA connector call names a connection reference that is not in ctx.flow.connectionReferences.Add it to the constructor, or fix the name.
CONNECTION_REF_MISSINGErrorLogic Apps JSON only: an OpenApiConnection action or trigger uses a connectionName that is not in connectionReferences.Add the reference.
PARAMETER_COUNTErrorMore than 50 parameters, counting $connections and $authentication. Cloud error: "The number of template input parameters limit exceeded".Group related values into one Object parameter.

Placement: where an action may sit​

CodeSeverityWhat triggers itFix
DSL037Errorctx.response() or ctx.terminate() inside a for...of, while or do...while loop, at any depth. Cloud error: "... has type 'Response' that could not be nested under an action of type 'foreach'".For a response, collect the result in a variable and respond once after the loop. For terminate, set a flag in the loop and terminate after it.
RESPONSE_NESTED, TERMINATE_NESTEDErrorSame rule on IR/JSON.Same.
DSL038Errorctx.response() with @RecurrenceTrigger or a polling @ConnectorTrigger (one with a recurrence). Cloud error: "The workflow with 'Response' action type should not have triggers with 'recurrence' property defined". A webhook connector trigger is fine.Remove the response (nobody is waiting for it), or use an HTTP, manual or webhook trigger.
RESPONSE_TRIGGERErrorSame rule on IR/JSON.Same.
RESPONSE_SCHEMA_MISMATCHErrorTwo Response actions answer the same status code and both declare a schema, but the schemas differ (key order aside). Cloud error: "The schema definitions for actions with same status code must match."Give both the same schema, or use different status codes.
NESTING_DEPTHErrorAn action nested deeper than level 8 (top-level actions are level 0). Cloud error: "... nested at level '9' which exceeds the maximum nesting limit of '8'".Flatten the logic, or move the inner part into a child flow.
DSL044WarningA 'PowerApp' response in a flow without @ManualTrigger, or a 'VirtualAgent' response without @HttpTrigger({ triggerKind: 'VirtualAgent' }). The cloud saves it, but the maker portal does not offer that combination.Match the response kind to the trigger.
RESPONSE_KINDWarningSame rule on IR/JSON.Same.
RUNAFTER_SKIPPED_ON_SUCCESSWarningNo Response action runs on the path where every action succeeds. The usual cause is a Response chained after a catch scope: on success the catch is Skipped, and so is the Response. An HTTP caller then gets no answer, and a parent flow fails with 502 BadGateway (NoResponse).Run the Response after a Finally scope (@runAfter Try: Succeeded, Failed, Skipped), or list both statuses of the catch scope (@runAfter Catch: Succeeded, Skipped).

A Response in a parallel branch is accepted by the cloud, so it is not reported.

runAfter​

CodeSeverityWhat triggers itFix
DSL021ErrorAn @runAfter status other than Succeeded, Failed, Skipped or TimedOut.Fix the status.
RUNAFTER_STATUSErrorSame rule on IR/JSON, or a runAfter entry with no status at all.List at least one valid status.
DSL022Error@runAfter names an action that does not exist.Fix the name.
RUNAFTER_UNKNOWNErrorIR/JSON: a runAfter target that is not a sibling in the same container. runAfter can only point at actions in the same scope or branch.Point at a sibling, or restructure.
RUNAFTER_SELFErrorAn action runs after itself.Remove the entry.
RUNAFTER_CYCLEErrorrunAfter entries form a cycle.Break the cycle.
RUNAFTER_SHAPEErrorA runAfter that is not an object.Fix the JSON.
DSL048ErrorAn action reads (ctx.body(), ctx.outputs(), ctx.actions(), ctx.result(), or the same functions in ctx.eval()) an action that is not on its runAfter path, or reads itself. Cloud error: "Action 'X' must either be in 'runAfter' path or within a scope action on the 'runAfter' path". An action may read only what finishes before it on its runAfter chain, or on the chain of a block enclosing it, including anything nested inside those. It may not read a parallel branch, the other branch of an if/switch, or a block that encloses it. An Until's condition may read its own body; an if, switch or loop header may not.Read the action after the branches join, or add it to the reader's @runAfter.
EXPR_RUNAFTER_PATH, EXPR_SELF_REFERENCEErrorSame rule on IR/JSON. In IR, a node without runAfter runs after its previous sibling; in Logic Apps JSON it starts its container.Same.
RUNAFTER_DEAD_STATUSWarningAn action lists a status that can never hold together with its other runAfter entries. runAfter entries are ANDed, so @runAfter Catch: Succeeded, Skipped plus @runAfter Try: Succeeded (where Catch runs after Try: Failed) never runs on the catch path: when Catch succeeded, Try failed. That action and everything chained after it is Skipped, and the run still ends Succeeded.Remove the conflicting status or entry, or run the action after one Finally scope that covers every path.
RUNAFTER_UNSATISFIABLEWarningNo outcome of the runAfter dependencies satisfies all of them at once, so the action can never run.Same.

Over-listing statuses is not reported: @runAfter Try: Succeeded, Failed, Skipped is fine even when one of those can never happen.

Expressions​

CodeSeverityWhat triggers itFix
DSL033ErrorAn expression inside ctx.eval('...') does not parse, or uses a double-quoted string (concat("a")). Power Automate expressions use single quotes.Fix the syntax.
EXPR_SYNTAXErrorSame rule on IR/JSON, for every @... string and every @{...} segment, including an unclosed @{.Same.
DSL049Errorbody(), outputs(), actions(), actionBody(), actionOutputs(), variables(), items() or result() in ctx.eval() without a name, or with a number as the name. The cloud refuses to save it.Pass the name as text: body('Get_items').
EXPR_ARG_COUNT (missing name), EXPR_ARG_TYPEErrorSame rule on IR/JSON.Same.
DSL050WarningA function in ctx.eval() is called with a number of arguments it does not take (add(1), createArray()). The cloud saves it, but the action fails with the function's own message when it runs.Fix the call. For an empty array write json('[]'), or [] in TypeScript.
EXPR_ARG_COUNT (wrong count)WarningSame rule on IR/JSON, for any expression in the flow.Same.
DSL034WarningAn unknown function in ctx.eval(). The cloud saves it and fails at run time; the local engine fails too.Check for a typo. There is no abs(), ceil(), floor() or round() in Power Automate.
EXPR_UNKNOWN_FUNCTIONWarningSame rule on IR/JSON.Same.
DSL004Errorctx.body(), ctx.outputs() or ctx.actions() names an action that is not declared before it.Fix the name, or move the reading action after it.
EXPR_UNKNOWN_ACTIONErrorIR/JSON: an expression references an action that does not exist (InvalidTemplate).Fix the name.
EXPR_LOOP_REFERENCEErroritems('X') or iterationIndexes('X') outside a loop named X. Cloud error: "repetition actions referenced by inputs are not defined".Use the enclosing loop's name.
EXPR_LOOP_REFERENCE (item())Warningitem() outside a foreach loop, Select, Filter array or Create table. The cloud saves it; there is no current item when it runs.Move it into a loop, or use items('Loop').
EXPR_LENGTHErrorA single expression longer than 8,192 characters (counted without the leading @, each @{...} on its own). Cloud error: "The template language expression limit exceeded".Split it across Compose actions.
CONDITION_NOT_STRINGErrorLogic Apps JSON: an If condition with { "not": "@empty(x)" }. The cloud refuses a not whose operand is an expression string. The emitter never writes this; it writes { "not": { "equals": ["@empty(x)", true] } }.Wrap the operand as a comparison.

Descriptions are never checked as expressions: Power Automate saves @{ or a leading @ in a note as plain text.

Control flow shapes​

CodeSeverityWhat triggers itFix
DSL041ErrorA switch with more than 25 cases.Split it into nested switches, or use a lookup object.
SWITCH_CASESErrorSame limit on IR/JSON.Same.
SWITCH_EXPRESSIONErrorIR/JSON: a Switch on plain text or a literal instead of an expression.Switch on an expression starting with @.
IF_CONDITIONErrorIR: an if node without a condition.Add one.
FOREACH_ITEMSErrorIR: a foreach node without itemsExpression.Add one.
UNTIL_LIMITErrorLogic Apps JSON: an Until without limit. Cloud error: "The property 'limit' is required on action type 'Until'". The emitter always writes one (60 iterations, PT1H by default).Add limit.count and/or limit.timeout.
UNTIL_EMPTYWarningAn Until with no actions: it only re-evaluates its condition until the limit is reached.Add the loop body.
UNTIL_COUNT, UNTIL_TIMEOUTWarningAn Until count outside 1–5,000, or a timeout that is not an ISO 8601 duration. The cloud saves both.Fix the value.
TERMINATE_STATUSErrorA Terminate status other than Succeeded, Cancelled or Failed.Fix it.
TERMINATE_RUNERRORErrorA Terminate with runError but a status other than Failed.Only pass a run error with 'Failed'.

Annotation values and limits​

CodeSeverityWhat triggers itFix
DSL043ErrorA value out of range in a JSDoc annotation: @runtimeConfig concurrency.repetitions outside 1–50 or concurrency.runs outside 1–100; @retryPolicy type other than none/fixed/exponential, count outside 1–90, or an interval that is not an ISO 8601 duration between PT5S and P1D.Fix the value.
DSL043 (@limit)Warning@limit with a count outside 1–5,000, or a timeout that is not an ISO 8601 duration. The cloud saves it.Fix the value.
FOREACH_CONCURRENCYErrorForeach concurrency outside 1–50.Fix it.
TRIGGER_CONCURRENCYErrorTrigger concurrency (runtimeConfiguration.concurrency.runs) outside 1–100.Fix it.
RETRY_POLICYErrorA retry policy that is not an object, has an unknown type, a count outside 1–90, or an interval that is not an ISO 8601 duration between PT5S and P1D. Intervals in milliseconds are not accepted.Use ISO durations such as "PT20S".
DSL040ErrorMore than 500 actions.Move part of the logic into a child flow.
ACTION_COUNTErrorSame limit on IR/JSON.Same.
PAGINATION_THRESHOLDWarningpaginationPolicy.minimumItemCount above 5,000 (or not a whole number from 1 to 100,000). Above 5,000 the designer reports the setting as invalid unless the flow owner has a premium Power Automate plan.Use 5,000 or less for flows that run on Microsoft 365 licenses.

Triggers​

CodeSeverityWhat triggers itFix
DSL045ErrorA @RecurrenceTrigger with an unknown frequency, an interval that is not a whole number or gives a period over 500 days, a startTime more than 49 years ahead, or a schedule whose next run is more than 18 months away.Fix the option.
DSL046ErrorA recurrence schedule with a frequency other than Day, Week or Month; weekDays without frequency: 'Week'; monthDays without frequency: 'Month'. Power Automate rejects these on save.Match the schedule to the frequency.
RECURRENCE, RECURRENCE_SCHEDULEErrorThe same rules on IR/JSON, for the Recurrence trigger and for a polling connector trigger's recurrence. Also an unparseable startTime.Same.

Connectors​

CodeSeverityWhat triggers itFix
DV_LOOKUP_UNGUARDEDInfoA Dataverse GetItem ("Get a row") whose recordId is a lookup column (_x_value) read from another action, with no enclosing if that checks that same column. When the lookup is empty, recordId is null and the action fails.If the lookup can be empty, wrap the call in if (!ctx.empty(...)) on the same value. A required lookup is fine as is.
SELECT_MAP_TEXTWarningA Select whose map is text rather than one expression, typically a template literal (`${ctx.item()?.['a']}-x` compiles to @{...} text). The cloud saves and runs it, but the designer reads a text map as JSON and flags the action with "Enter a valid JSON".Make the map one expression: ctx.concat(ctx.item()?.['a'], '-x').
HTTP_RETRYInfoIR: an HTTP action without retryPolicy. flowforger validate does not print it for .ff.ts files, since the cloud applies its default policy.Nothing to do, unless you want a specific policy.
IR_HTTPErrorIR: an HTTP action without method or url.Add them.
CONNECTOR_FIELDS, CONNECTOR_WEBHOOK_FIELDSErrorIR: a connector node without connector or operation.Add them.
SP_PARAMSErrorIR: a SharePoint GetItems, GetItemById, CreateItem, UpdateItem or DeleteItem without its site (dataset), list (table), item id or fields.Add the missing parameters.
DV_PARAMSErrorIR: a Dataverse node with operation ListRows, CreateRow, UpdateRow, DeleteRow or RetrieveRow without its table, row id or body.Add the missing parameters.
APPROVAL_PARAMSErrorIR: an Approvals StartAndWaitForAnApproval webhook without approvalType.Add it.

SP_PARAMS and DV_PARAMS only look at the operation names listed. The typed Dataverse operations (ListRecords, CreateRecord, ...) and most SharePoint operations are not checked this way.

Codes that are not reported​

  • DSL031 (retired): "description exceeds 255 characters". Long comments are no longer an error: the emitter writes a 256-character excerpt and keeps the full text in metadata. The number is not reused.
  • DSL036 (retired): "comment starting with @". Power Automate saves such a description as is. The number is not reused. DSL035 used to mean "@{ in a comment" and now means an invalid @action name.
  • DSL009, DSL010, DSL011, DSL012, DSL013 are defined in the language service but no current rule reports them. Their topics are covered by other codes: loop references by EXPR_LOOP_REFERENCE, IR validation by flowforger validate.

See also​