Skip to main content

Job dependencies

See How workflows work.

Task walkthrough: Order jobs with dependencies. This page is the full configuration and troubleshooting reference.

A job runs only when all of its dependencies are satisfied. Dependencies are how a workflow is sequenced and how jobs coordinate shared state and capacity.

Dependency types​

TypeSatisfied whenKey settings
JobA predecessor job behaves as the two dependency axes require (see below)Predecessor Workflow + Job Name, Condition (success / failure / any), Type (requires / after / conflict), Days Offset
ThresholdA named threshold compares as required against a valuethreshold, operator, thresholdValue — see Threshold dependencies
ResourceEnough of a named resource is available (the job reserves it)resource, resourceRequired
ExpressionA custom expression evaluates to Trueexpression — see Expression dependencies

The two axes of a job dependency​

A job dependency has two independent axes. Set both on the job editor's Dependencies tab, or on the workflow canvas when you draw the dependency:

  • Condition — which result of the predecessor satisfies the dependency: Finished OK (success), Failed (failure), or Any (any, the ignore-error union).
  • Type — what happens when the predecessor is absent from the schedule for the resolved date:
TypePredecessor is presentPredecessor is absent for the date
Requires (default)Waits for it to reach the ConditionWaits — the dependency is not met
AfterWaits for it to reach the ConditionProceeds — the dependency is treated as met

Use After for "run after this if it runs" ordering, so an optional or conditionally scheduled predecessor doesn't stall the dependent on a day it isn't scheduled. Use Requires (the default) when the predecessor must actually run.

"Absent for the date" has three causes, and the dependency card names all three: the predecessor's frequency didn't fire for this date, the predecessor is Disabled, or it isn't defined in the workflow at all. A Requires dependency on an absent predecessor waits until an operator adds that predecessor to the instance.

A third Type, Conflict, inverts the relation entirely — it is about not running at the same time rather than about order. It is covered below.

A dependency with no explicit Type behaves as Requires and keeps its previous behavior. Editing the job requires you to set a Type — pre-filled Requires — before you can save.

Days Offset​

Days Offset (−365 to +365) lets a job depend on a predecessor from a different day's run: 0 is the same day, −1 the previous day, +1 the next day. The offset resolves against that day's instance of the predecessor, so a job can wait on — or run after — yesterday's or tomorrow's run of another job.

A self-reference at a non-zero offset is allowed and points at the job's own prior or next dated run. A job cannot depend on itself on the same day (same workflow, same job, offset 0); that combination is now flagged on the dependency card as you author it and blocks the save, rather than only being refused by the platform once you try to save.

Conflict — keeping two jobs off each other​

Requires and After both say "run in this order." Conflict says something different: this job must not run while that job is running. It constrains concurrency, not order.

Use it for the classic mutual-exclusion cases — two jobs that must not hit the same core file at once, or a singleton job that must never overlap another copy of itself on a different schedule date.

A Conflict dependency behaves unlike the other two in several ways:

Requires / AfterConflict
ConditionRequired (success / failure / any)None — a conflict has no Condition, and setting one is rejected
Satisfied whenThe predecessor reached the ConditionNo matching job is currently running
Predecessor absentWaits (Requires) or proceeds (After)Proceeds — nothing to conflict with
Name matchingExact job nameExact, or a name prefix (jobNameLike) so one rule covers a family of jobs
Day scopeThe date the Days Offset resolves toThat date, or every scheduled date (allDays), which ignores Days Offset

Two more behaviors worth knowing:

  • A job may hold both a Conflict and an ordering dependency on the same predecessor. "Wait for X, and also don't run alongside X" are two different statements, so they are kept as two records.
  • A job may conflict with itself. A same-day self-conflict has nothing to collide with but itself, and settles as satisfied. Combined with allDays, a self-conflict is the standard way to express "only one copy of this job runs at a time, across every schedule date" — the ordinary singleton guard.

A job is held while a matching job is running, or has finished but not yet reached its final status. Once no match is running, the dependency is satisfied and the job proceeds.

How the conflict's workflow name is matched

A conflict that names its own workflow is recognised as a same-workflow conflict whatever the capitalization and surrounding spaces — payroll and PAYROLL are the same workflow, and the conflict holds the job as intended.

A conflict naming a different workflow is matched case-sensitively. If the name does not match a workflow that is built and resident, there is nothing to conflict with and the dependency is satisfied, so the job runs. Copy the predecessor's workflow name rather than typing it from memory: a name that differs in case names nothing, and the conflict will not hold the job.

When several jobs conflict with each other​

If two or more jobs declare conflicts on one another and all of them are waiting on that conflict — none actually running — the platform releases them one at a time rather than releasing the whole set together. The rest stay waiting and are reconsidered on the next pass.

Good to know

Either way, only one member of a conflicting set is ever dispatched at a time. That guarantee is enforced where jobs are dispatched, not by the order they leave the waiting state, so a conflict does its job even when several members become eligible in the same pass.

Authoring a conflict​

Set Type to Conflict on a dependency, the same way you set Requires or After — on the job editor's Dependencies tab (from either the Workflows page or the Processes page) or in the New Dependency dialog. Because a conflict carries no Condition, that field disappears when you choose it.

When you turn on prefix matching, the Job Name field becomes free text: you are naming a pattern that jobs start with, not picking one existing job.

On the Workflows canvas a conflict is drawn as its own edge — dashed and without an arrowhead, since it expresses no order. A self-conflict draws as a loop on the job itself, and a prefix conflict draws against a node standing for the pattern rather than any one job. A conflict on a job in another workflow shows as a badge on the job.

Good to know

When a job is held by both an unmet ordering dependency and a conflict, the ordering dependency is the one reported as the reason it is waiting. A conflict is a transient "something else is running right now", so surfacing it over a genuine unmet precondition would send you after the wrong thing.

Choosing a predecessor​

A predecessor can be in the current workflow or another workflow — pick the workflow, then the job. When you edit a job dependency inside a running instance (from Processes), the predecessor's workflow is pinned to that instance's own workflow.

If a predecessor can't be resolved, the dependency shows a warning naming the cause — either the job is absent from the dated schedule, or it exists but under an instance name this job doesn't share.

A dependency records the predecessor's identity​

A job dependency stores two things about the job it names: that job's persistent id and its name. The id is the primary key of the pair, and it is what lets a dependency survive a rename of the job it points at.

The editor fills both in for you, and the rules that follow only matter when you edit a configuration by hand or through the API.

How the pair is resolved when a workflow is saved:

  • The id wins when it resolves to exactly one job. The name beside it is then refreshed from that job — so editing the name alone does not re-point the dependency. To aim a dependency at a different job, clear the id in the same edit, or just re-pick the predecessor in the editor.
  • An id that resolves to nothing falls through to the name, and the name's own job then supplies a fresh id. A dangling id is repaired rather than trusted.
  • An ambiguous id or name is declined. If two jobs somehow answer to the same id, or two share the name, the reference is left name-only and picks up an id on a later save, once the ambiguity is gone.
  • A prefix reference never gets an id. A conflict authored against a name prefix matches a set of jobs whose membership changes as jobs are added and renamed, so there is no single identity to point at. Those references stay name-based permanently — see Authoring a conflict.

What that means for a rename. The dependent workflow's copy of the predecessor's name is brought up to date the next time that dependent workflow is saved. Until then the stored configuration — and therefore any version built from it — still carries the old name. So after renaming a job that other workflows depend on, save those workflows and create new versions of them.

Errors a hand-edited dependency can now hit at save time:

MessageMeaning
Job 'X' not found in current workflowA same-workflow dependency names a job this workflow doesn't have.
Job 'X' not found in schedule 'Y'A cross-workflow dependency names the workflow by name, and that workflow has no such job.
A workflow reference that resolves to neither an id nor a nameThe save fails. The workflow a dependency names is the dependency — a reference to a workflow that cannot be found is never stored.
Resolving this workflow's references rewrote a dependency to point at a renamed job or schedule…Refreshing the stale names produced a duplicate dependency, or a dependency on the job's own self. Fix the named dependencies and save again.

One deliberate exception: a cross-workflow dependency whose workflow id resolves but whose job is missing does not fail the save. That is exactly the reference a renamed predecessor strands, and refusing it would block the save that repairs it. It still fails at build.

A copy of a workflow drops the ids of dependencies pointing at the copied workflow's own jobs — those get fresh identities — and keeps the ids of dependencies pointing into other workflows, whose jobs are untouched. An import drops both, because a bundle's ids belong to the system it came from; the names carry over and the ids repopulate on the first save.

A deployment transformation rule may not change a dependency's job id, though the name beside it stays transformable — see Fields no rule may change.

A predecessor that fanned out into several instances​

A wait-on-all dependency on a multi-instance predecessor is stored as one record per matched instance. In the Processes job instance editor those are shown as a single dependency card, named for the underlying job, rather than one card per instance — so a dependency on a job that expanded into three instances reads as one dependency, which is what it is.

Editing or deleting that card applies the change across every underlying instance when you save.

Cards are only combined when the underlying records agree on their Condition and Type. If they diverge — different conditions against different instances of the same predecessor — they stay as separate cards, because collapsing them would hide a real difference.

When the workflow builds as several schedule instances​

A workflow with Allow Multi-Instance on builds once per named schedule instance for a date. Dependencies and conflicts resolve within one instance, because instances are independent runs over different property values:

ReferenceResolves to
Same workflow, same dateThis job's own instance. BRANCH1's job is not held by BRANCH2's.
Same workflow, a day offsetThe same instance name on that date — every run of it.
Same workflow, conflict marked for every dateOnly this instance's dated runs. A singleton job stays a singleton per branch.
Another workflow, with an instance named on the dependencyThat instance, and every run of it.
Another workflow, no instance namedThe predecessor's first instance — the lowest name, computed over the instances that workflow defines — and its first run.
Another workflow, conflict, no instance namedEvery instance built for that date.
An instance can be built more than once for a date

A schedule instance can hold several runs for one date — see Repeat builds of one instance. Wherever a reference names an instance, it means the instance, not one run of it: a conflict sees every run, so live work on a later run is not overlooked and reported satisfied. A plain dependency that names no instance resolves to the first instance's first run, which is the run the workflow's own schedule produced.

Two consequences are worth planning around:

  • A cross-workflow dependency with no instance named is not "whichever built first". The first instance is computed from the workflow's definition, not from the rows that exist at the moment the dependency is evaluated — so a fan-out part-way through cannot settle a dependent on the wrong branch. That matters because dependency resolution is sticky once settled.
  • To make two branches contend, ask for it. Same-workflow conflicts are per-instance. Use a Resource dependency or a cross-workflow conflict when branches genuinely share something — both of those span instances.

Seeing a predecessor from outside the instance​

A predecessor outside the waiting job's own workflow instance — in another workflow, or in this workflow on a different day — is drawn on the Processes page's workflow diagram as a dimmed stand-in card rather than being left off the canvas. It shows the workflow name, the job name, the schedule instance where one is named, and the Days Offset; it carries no status, because nothing on that canvas resolves it. See Predecessors outside this run.

An any-day conflict's stand-in is worth reading carefully: its arrow reports blocked whenever a match is live on any date, including dates the canvas is not showing. Over-reporting a block is the deliberate direction there.

A cross-workflow predecessor on the Design canvas​

The Design module's workflow canvas draws the same idea for the job being authored. A dependency on a job in another workflow gets its own card — the job name on the first line, the workflow it lives in on the second — with an edge into the job that depends on it.

BehaviourDetail
One card per external jobEvery dependency naming the same workflow.job pair shares one card; each dependency still draws its own edge.
Edge style follows the dependencyA Requires or After dependency draws the ordinary ordering edge, result colour and day-offset label included. A Conflict draws the conflict edge.
A prefix conflict is its own cardA conflict that names a job-name prefix rather than a job shows the prefix with a trailing *, and is a separate card from a real job of that name.
Display-onlyThere is no ⋮ menu and nothing to delete: the dependency is edited on the job editor's Dependencies tab.
Show or hide themThe Card Filters section has an External Jobs switch, beside Frequencies, Thresholds, Resources and Expressions. See The workflow editor tool panel.

Selecting the card opens that workflow in a new tab. The whole card is the target — it takes keyboard focus, and Enter or Space opens it too. A double-selection opens one tab, not two.

The card carries only the workflow's name, so that is what is resolved. If it cannot be, a message says which case it was: the dependency no longer names a workflow, the workflow was not found, too many workflows have similar names (open it from the workflows list instead), or the browser blocked the tab.

note

A dependency authored against this workflow's old name — renamed but not yet saved — reads as external until the save, and appears as one of these cards. It is the same name-based comparison the rest of the canvas uses.

Dragging across the card still pans the canvas, so a selection that drifts more than a few pixels is read as a drag and does not open anything.

How job dependencies resolve​

  • Condition matters: success waits for the predecessor to finish OK, failure waits for it to fail, and any proceeds on either. A dependency on failure/any is valid design (e.g. a cleanup job that runs when its predecessor fails).
  • Type decides the absent-predecessor case only — see the two-axis table.
  • Days Offset lets a job depend on a predecessor from a different day's run.
  • Resource dependencies reserve capacity — a job waits if not enough of the resource is free.

Which predecessor statuses satisfy each Condition​

A Condition is not a single status — each maps to the set of terminal statuses that release the dependent:

ConditionSatisfied byNotes
successFINISHED_OK, MARKED_FINISHED_OK, SKIPPED, FIXEDA skipped or marked-fixed predecessor counts as success.
failureFAILED, MARKED_FAILED, INITIALIZATION_ERROR, UNDER_REVIEWUNDER_REVIEW is a deliberate non-terminal release point — the operator has acknowledged the failure, so recovery work starts without waiting for it to be marked Fixed or Failed.
anyThe union of the two lists aboveany is the ignore-error union, not "any outcome."
  • CANCELLED and MISSED_START_TIME satisfy nothing under any condition — a predecessor that never produced a result cannot release its dependents. A job depending (even with any) on a predecessor that ends up cancelled or missing its start time will wait forever; that's the intended behavior, not a bug. Clear it with Force Start / Skip or fix the upstream job.
  • *_PENDING_TERM statuses do not satisfy dependents — the predecessor must reach its final terminal status first.

Threshold dependencies​

A threshold dependency holds the job until a named threshold compares as required against the value on the dependency. You choose the comparison:

OperatorSatisfied when the threshold is
Equal Toequal to the value
Not Equal Toanything but the value
Less Thanbelow the value
Less Than or Equal Toat or below the value
Greater Thanabove the value
Greater Than or Equal Toat or above the value

Greater Than or Equal To is what a dependency with no operator set means, so a dependency authored before the operator existed keeps the behavior it had.

The workflow canvas draws the operator on the dependency's edge — ≥ 5, < 10 — so the comparison the job is waiting on is readable without opening the job.

Only a threshold confirmed deleted is skipped — everything else waits

If the threshold a dependency names has been deleted, the dependency is treated as satisfied and the job proceeds. That keeps a deleted threshold from silently parking every job that referenced it.

That is the only case that releases the job. Anything else that stops the value being read — the threshold is not found, nothing is configured, a refusal, a service error — leaves the dependency blocking, because the threshold may exist and the answer is unknown. Releasing a job cannot be undone, so an unreadable threshold fails closed.

Two consequences follow from that, and they are the ones worth knowing:

  • A renamed threshold blocks. A dependency saved without the threshold's identifier is looked up by name instead, and a name that matches nothing is "not found" rather than "deleted" — so the job waits rather than being released. Every dependency authored in the current job editor carries the identifier, so this affects imported ones.
  • A threshold identifier that never existed blocks too, for the same reason. An automation import keeps the identifiers from the bundle it came from, so a dependency can arrive naming one this environment has never had.

A dependency carrying neither an identifier nor a name blocks without looking anything up.

Expression dependencies​

An Expression dependency holds the job until a custom expression evaluates to True. The job waits at WAIT_EXPRESSION_DEPENDENCY while it is being asked, and the platform re-asks it about once a second until it answers True — an expression is never a once-and-done fact, so a job that satisfied it and then restarted on a recurring cycle is asked again.

All of a job's expressions are evaluated as one expression​

A job's expression dependencies are joined with && and evaluated as a single expression, not evaluated separately and then ANDed. Line breaks and tabs are removed from the joined text rather than replaced with a space, so an expression split across two lines is rejoined with nothing between the halves.

caution
Join your own parentheses if a half contains ||

&& and || share one precedence level and associate left to right, so a half written [[OI.A]] = "1" || [[OI.B]] = "1" joined with a second half reads (A or B) && second — which is usually what you meant — but the same associativity makes first || second && third read (first || second) && third. Parenthesize each half you author, and prefer one expression dependency carrying the whole condition over several that have to be read together.

The stored value is the expression body. A pasted [[= … ]] wrapper is stripped from each expression before the join, so authoring it either way works.

Only True and False answer the gate​

The rendered result is read with the same parse as the ToBool operator: true or false, case-insensitively, with surrounding whitespace tolerated. Nothing else counts.

caution
1 is not true, and 0 is not false

A number is refused outright, and so is a non-empty string — there is no truthiness here. An expression that computes 1 + 0 does not satisfy the dependency; it puts the job on hold. Write a comparison — [[= 1 + 0 > 0 ]] — so the expression renders True.

What each outcome does to the job​

The expressionWhat happens to the job
Renders TrueAdvances to the next waiting status, and on to dispatch when its other dependencies are met
Renders FalseStays at WAIT_EXPRESSION_DEPENDENCY and is asked again on the next pass. This is the normal steady state of a gate that is not open yet
Is malformed, renders something that is neither True nor False, or renders nothingThe job is put ON_HOLD, and the reason is recorded as its termination description
Could not be answered because something it reads was temporarily unavailableStays at WAIT_EXPRESSION_DEPENDENCY and is retried on the next pass — the same as False. A transient fault is not treated as a bad expression

A job carrying no expression dependency is unaffected by any of this.

A held job names its own cause without log access

The failure text is the job's termination description, so it appears on the job detail panel, on the job's node on the workflow diagram, and as the marker beside its name in the jobs list. It leads with the cause and then names the authored expression, and it never contains a resolved value — so an expression comparing an encrypted property does not leak that property into an operator-visible message. Very long text is truncated.

Release the job once the expression is corrected. A job that was held, released and then ran successfully no longer carries the old failure text into its own result.

A hold's reason can outlive the problem

The reason is written only when the job changes status. If a held job is released and its expression is then merely False, the job parks at WAIT_EXPRESSION_DEPENDENCY still showing the earlier failure text. Read the status, not the text, as the current state.

An expression is resolved strictly, and against stored values​

Two things about resolution differ from an expression in a job parameter:

  • There is no degraded reading. A token the platform cannot resolve fails the expression instead of rendering as its own source text, because source text that happens to read True would open the gate. So a misspelled property name holds the job.
  • Operands are not masked. The comparison runs against the property's stored value, so an encrypted property compares as itself rather than as a mask. The result is a True/False the platform acts on and never a value it stores or sends, which is what makes that safe.

Which run facts an expression can read​

An expression dependency is evaluated before the job is matched to a machine, so the job's own facts read slightly differently here than they do in a job parameter at dispatch:

TokenAt the gate
JI.$MACHINE NAMEAlways available. Undetermined until the job has a machine
JI.$ACTUAL RUN TIMEEmpty — the job has not run
JI.$MASTER JOB NAMEThe job's base definition name, so a job expanded into instances reads the name it was expanded from. It is the stored name and does not follow a later rename
JI.$EST RUN TIMEAvailable only if the job instance carries an estimated run time
OI.$OPCONVERNot available at the gate — a tenant property of the same name is read instead
caution
$EST RUN TIME is not seeded by the build

A built job carries no estimated run time, so an expression that reads JI.$EST RUN TIME cannot be resolved and holds the job until someone sets Estimated Run Time on that job instance. Clearing that field on the instance takes the value away again. Don't gate a job on this token.

A job marked to be skipped still resolves its expression​

A deferred skip (JOB_TO_BE_SKIPPED) resolves its expression on the same gate rather than bypassing it, so a skip lands in the same order relative to the expression as a run would have. An expression that cannot be evaluated there does not put the job on hold — the skip you asked for is preserved, and the job stays marked.

Troubleshooting​

SymptomLikely causeResolution
Job sits waiting and never runsA dependency isn't satisfied — identify which oneCheck each dependency: predecessor result, threshold value, resource availability, expression (Operator → Builder if the dependency is mis-set).
A job with an expression dependency is On Hold, with a reason naming the expressionThe expression could not be evaluated, or it rendered something that is neither True nor FalseFix the expression on the job and release the job. See What each outcome does to the job.
A job with an expression dependency is held, and the reason names an estimated run timeThe expression reads JI.$EST RUN TIME, which the build does not populateSet Estimated Run Time on the job instance, or gate the job on something else (Builder).
A job with an expression dependency waits with no reason recordedThe expression is rendering False — the gate is simply not open yetExpected. Check what the expression reads; a held job would have named a cause instead (Operator).
Job waits on a predecessor that already finished OKThe dependency expects failure or a different Condition/day offsetConfirm the Condition and Days Offset match intent (Builder).
Job waits even though the predecessor isn't scheduled todayThe dependency is Requires, which waits when the predecessor is absentSwitch the Type to After if the predecessor is optional; leave it Requires if it must run (Builder).
Job waits regardless of Type, with a warning on the predecessorThe predecessor exists but only under an instance name this job doesn't share; cross-instance dependencies aren't allowedCompare the two jobs' instance names and re-point the dependency (Builder).
A job in one branch waits on a predecessor that ran in another branchThe reference names no instance, so it resolved to the predecessor workflow's first defined instanceName the instance on the dependency, or keep the branches in the same workflow so same-workflow scoping applies (Builder).
Job waits only while another named job is running, then proceedsA Conflict dependency is holding it — by design, to stop the two overlappingExpected. If the two should be allowed to overlap, remove the conflict (Builder).
Job never gets a resourceNot enough of the named resource is available; another job holds itCheck resource counts and which jobs hold it (Builder / Operator).
Job runs earlier/later than expected relative to anotherMissing or extra job dependencyReview the dependency wiring (Builder).
A job sits in Waiting on start time forever, with nothing logged and no status explaining itA start-time window was set through the API or an automation import with a bound that isn't a time. An unreadable bound never opens the window, and an unreadable end reads as permanently pastSuch a request is now rejected, and the error names the offending bound. A job already stuck this way needs the schedule date rebuilt (Administrator).

Contact support when​

  • All dependencies are demonstrably satisfied but the job still does not become eligible.

Include the workflow instance, the job, and the state of each of the job's dependencies.