Skip to content

CALL rule

Runs another rule from inside this one, at the point where the op sits, and then carries on with the next op. The called rule is not a copy and not a sandbox: it works on the same stash as the caller, so everything it writes is waiting for you when it returns.

This is how a set of pipelines share one piece of logic. Put the common part in its own rule, call it from each pipeline, and fix it in one place. The alternative op, INCLUDE rule, grafts the other rule's ops into this rule when the rule is built, which flattens both into a single unit. Prefer CALL when the shared rule is maintained on its own schedule, and INCLUDE when you want the ops themselves to show up as part of this tree.

calling rule ops before CALL rule ops after calls returns called rule its own ops run here logged under its own name one stash: what either side writes, the other side reads

Fields

Rule To Be Invoked

The rule to run, picked from a searchable list. Type to filter by name; the list pages ten at a time and is sorted by name. One rule per op, and the field is required: the designer refuses to apply the op while it is empty.

The list offers active rules, with one exception: type a rule id exactly and an inactive rule with that id shows up too. That exception matters more than it sounds, because the active flag is never checked again afterwards. A rule you deactivate later keeps running for every op that already points at it. Deactivating a rule hides it from the list and stops it being chosen as a pipeline; it does not stop it being called.

Any rule type can be selected. Calling a pipeline rule from outside a job gives it a stash with no job in it, and the first op that expects one fails.

What the form stores is the rule id, but the value is expanded against the stash before the lookup and the lookup accepts an id or an exact rule name. That only shows up on rules you edited through the YAML view or imported, where ${target_rule} or a plain rule name in this field both work. Naming a rule that no longer exists stops the rule with a not-found message.

Name

The op's name on its Config tab, not part of this form. It is the label the tree and the job log show for the call, and it defaults to CALL rule, which tells a reader nothing. Name it after what the called rule does. The name is expanded against the stash before it reaches the log, so Call ${check_scope} checks prints the resolved text.

What the called rule sees

It sees your stash, the same one, not a copy. There is no parameter list: set the variables it needs before the op, with SET VAR or a code block, and read its results out of the stash afterwards.

One thing changes in the stash at the moment of the call. Global variables are merged in, and only under names the stash is not already carrying, so a value you computed into deploy_user survives and a deploy_user the caller never set arrives at its global default. The environment used is the bl in the stash, or the job's environment when the stash carries no bl, or the all-environments set when there is neither. Keys added this way stay in the stash after the call returns, so the caller can read them too.

What comes back

Whatever the called rule left in the stash. That is the entire return channel.

Return Key on the op's Options tab does nothing here. You can fill it in, save, and never see a value appear under that name. Read results out of the stash instead.

Each op of the called rule appears in the job log under its own rule name, so the log tells you which rule produced which line. The Rule Trace panel in the log viewer is a different matter: it draws the job's own rule, and this op holds no children, so nothing appears under the call there. Where the called rule does show up nested is rule profiling, which expands the call into the called rule's ops one level deeper and subtracts their time from the call's own. An op left in a parallel mode is not expanded.

Failure, versions and depth

If the called rule fails, the failure travels back out and stops the calling rule with a message that quotes the id of the rule that failed, not its name, followed by the original error. Trap it like any other op, with Error Handling on the Options tab or by wrapping the op in TRY statement and CATCH statement.

The called rule is resolved when the op runs, so it is always the current version that runs. A job pinned to a specific version of its pipeline does not pin the versions of the rules that pipeline calls. If a call has to be reproducible, treat the called rule as part of the pipeline's release surface and change it with the same care.

Nothing stops a rule calling itself, directly or through a chain. There is no depth limit and no loop detection; a cycle keeps calling until the process dies. Use a stash counter and IF var condition THEN if you need recursion at all.

Combining with other ops

Set up the inputs with SET VAR immediately above the call, so the contract is visible in the tree rather than buried in the called rule.

Guard the call with IF var condition THEN when the shared rule only applies to some environments or some topic statuses, and keep that decision in the caller. A called rule that starts with its own guard is harder to follow from either side.

Inside a JOB STEP block, a call runs as part of that step, and the called rule's ops take part in the same rollback bookkeeping as anything else in the step.

Examples

A shared pre-flight rule used by several pipelines. The caller states what to check, the called rule decides how.

SET VAR    check_scope = changesets
SET VAR    check_strict = 1
CALL rule  Rule To Be Invoked: Pre-flight checks

IF var condition THEN
  When: Any
    check_errors   NOT EMPTY
  FAIL   Pre-flight failed: ${check_errors}

A notification rule reused from a workflow rule and a pipeline alike. Everything it needs travels in the stash, and nothing comes back.

SET VAR    notify_subject = Deployment of ${job_name} finished
SET VAR    notify_group = release-managers
CALL rule  Rule To Be Invoked: Send release notification