Skip to main content

Workflow Container job

Behavior-level reference.

Task walkthrough: Embed another workflow with a Workflow Container. This page is the full configuration and troubleshooting reference.

The Workflow Container embeds another workflow as a step inside the current one. The referenced workflow is built when the parent is built, and runs when the container job runs; the container stays running until that nested workflow finishes. It's an internal job type (internal:workflow-container) — it runs inside the platform, with no agent and no connection.

Troubleshooting reminder: when reviewing a workflow, a single job may actually be an entire child workflow. Follow the container to the nested instance.

PropertyValue
Job typeWorkflow Container (workflow-container)
Runs onThe platform — no agent
Connections requiredNone
CategoryInternal

Configuration reference​

The field is labelled Sub-Workflow on the job, and what it holds is a reference to a workflow rather than a copy of one.

ParameterTypeRequiredNotes
workflow.namestringYesName of the sub-workflow to embed.
workflow.idstringNoThe workflow's identifier — optional, used for faster/exact lookup.

Choosing the sub-workflow​

In the Design module, the reference is a searchable picker — Search sub-workflows… — that searches the tenant's workflows as you type, and stores both the name and the id of the one you pick. The picker offers only workflows marked Sub-Workflow (see below), and the workflow you are currently editing is excluded from the list, so the picker cannot be used to create a trivial self-reference. (Deeper cycles are still caught at run time — see below.)

The search runs only while the list is open, and the query resets each time you open it, so a field that already holds a workflow opens onto the full list rather than filtering down to its own value. If the search itself fails, the picker says so rather than showing an empty list — an empty result and an unreachable search look identical otherwise.

In the Processes module the reference is shown as an editable object rather than a picker, so an instance keeps workflow.id visible and editable alongside the name.

Sub-workflows​

A workflow that exists to be embedded can be marked as one. On the workflow's Overview panel in the Design module, the Multi-Instance section leads with the Sub-Workflow switch, which declares that this workflow runs only as a container's child — "Only run by a Workflow Container Job in another workflow. Does not appear in the Schedule Workflow dialog." Turning it on:

  • Hides the workflow from the Schedule Workflow dialog, so it can't be picked for a direct run.
  • Refuses a direct build — building it on its own reports "This workflow is marked as a sub-workflow and can only be run by a container job."
  • Forces Allow Multi-Instance on, and locks it. One parent can invoke the same sub-workflow more than once on a date, so a sub-workflow has to be able to carry more than one instance. Turning Sub-Workflow back off leaves Allow Multi-Instance on — clear it yourself if you want it off.
  • Puts the workflow in the container picker. The container job's picker lists sub-workflows only.
  • Removes the AutoBuild Settings section, and clears what it held. A sub-workflow runs only when a container job runs it, so the nightly build skips it — an auto-build setting on one was a promise the platform never kept. The section is absent rather than disabled, and turning the switch on clears autoBuildSettings in the same change that forces Allow Multi-Instance on, so the stored configuration says what the panel shows. Turning Sub-Workflow back off brings the section back with Auto Build off: re-enabling it is a decision you make with the section in view, the same reasoning that leaves Allow Multi-Instance alone on that path.

The Sub-Workflow switch sits above Allow Multi-Instance because turning it on is what locks the switch below it, and both carry their explanation whether or not the flag is set.

Saving a version warns you which parents it affects​

Committing a version of a sub-workflow changes how every workflow embedding it behaves on its next build. The Save to a New Version dialog says so, above the deployment notice:

Changes will impact all workflows that use this as a sub-workflow. Used by: Parent A, Parent B, Parent C, +1 more

  • Only container references count. A cross-workflow dependency on this workflow is not an embedding and is not listed.
  • Parents are named, de-duplicated and sorted, with the first three shown and the rest as a count.
  • It appears only where there is something to warn about: a sub-workflow no container embeds gets no warning, and neither does an ordinary workflow.
  • Save is never blocked by the lookup. If the referencing workflows can't be read, the sentence is shown without names rather than being suppressed — a failed lookup is not evidence that nothing depends on this workflow.
  • The warning is driven by the flag as stored as well as as edited, so the save that turns Sub-Workflow off still warns. That is the edit the parents still embedding this workflow most need to know about.
  • The first version commits with no dialog, and so carries no warning. See Workflow versions and deployments.

Sub-workflows in the workflow list​

The Design module's workflow picker separates the two kinds rather than mixing them:

  • Results are grouped under Workflows and Sub-Workflows, each loading more of its own kind.
  • Filter chips for the two kinds narrow the list. Both are on to begin with; un-pressing one leaves the other, and un-pressing the last one is ignored, so the list is never empty. The choice is remembered for the browser session only — it is a browsing convenience, not a setting.
  • A sub-workflow row carries a Sub-Workflow badge after its name, so a workflow named without a suffix convention is still identifiable.
  • A Recent section lists recently opened workflows, narrowed by the same chips.

Following a container to its sub-workflow​

From a Workflow Container job on the Design canvas, Open in New Tab opens the sub-workflow it references in a new browser tab. It is offered from the job card's own menu and from the canvas context menu, on a single selected container job that holds a reference.

Two things about it are worth knowing:

  • It survives read-only. Opening changes nothing, so it is the one action still offered when the canvas cannot be edited — where the card's menu then holds just that item.
  • It reads the reference you can see. The reference is taken from the job as it stands in the editor, so a target you changed moments ago in the job editor is the one that opens, not the one last saved.

A reference that names a workflow by name alone is resolved the same way the build resolves it — trimmed and case-insensitive. When that cannot be settled, the page says which case it hit rather than opening the wrong workflow: the reference names nothing ("Sub-workflow 'X' was not found."), too many workflows have similar names to tell them apart, the job no longer references a sub-workflow at all, or the browser blocked the tab.

An embedding counts as a reference to the sub-workflow​

A workflow's Cross Reference lists every job that refers to it. That used to mean cross-workflow dependencies only, so a workflow embedded by three container jobs and depended on by none reported nothing referring to it — and could be deleted out from under all three.

A Workflow Container job that embeds the workflow is now one of those references. It appears in the Cross Reference list alongside any cross-workflow predecessors, it counts toward the total, and it blocks the delete the same way a dependency does. A job that both depends on the workflow and embeds it is listed once.

The reference is matched the way the build matches it: by stored id when the container holds one, otherwise by name, trimmed and case-insensitive. So "what refers to this workflow" and "what would run this workflow" are the same answer — which is the point. A container that holds an id but no name is counted too, even though the build would refuse it, because the author did point the job here.

Good to know

The flag governs how a workflow may be started, not what a container may reference. A container that already points at an ordinary workflow keeps working, so you can adopt the flag on the workflows you want protected without revisiting every container first.

A deploy transform rule can't rewrite the Sub-Workflow flag — it's a protected field. A rule that tried to add, remove, or rename it is rejected, because the Schedule Workflow dialog reads the stored workflow while the build reads the transformed one, and a rule that changed the flag would make the two disagree about whether a workflow can be run. The sub-workflow reference itself is protected for the same class of reason — see below.

An embedding constrains where each workflow can be deployed​

Because the parent cannot run its container job unless the child is deployed alongside it, the platform holds the two deployments together:

  • A parent cannot be deployed to an environment where a container job's sub-workflow is not deployed, or is deployed only for part of the parent's date window.
  • A child's deployment cannot be removed, deactivated, or narrowed while a live parent deployment in that environment uses it.
  • Renaming the child is refused while a deployed parent references it by name alone; deleting it is refused while any deployed parent references it, including a PINNED parent serving an older version that still embeds it.
  • A deploy transform rule cannot retarget the reference (taskDefinition.parameters.workflow, or the id or name beneath it). The deployment check reads the reference as stored and a rule is applied after it, so a rule here would point the build at a workflow nobody checked.

The Deploy modal shows this per environment before you act, as a Sub-Workflows section on a parent's cards and a Used By section on a child's. For the coverage rule, the exact messages, and what each card disables, see A sub-workflow must be deployed wherever its parent is.

How it works​

A container's nested workflow is built during its parent's build, not when the container job dispatches. When you build a workflow that contains a container job, the platform builds that container's sub-workflow as part of the same build, and recurses — so a three-level design is fully materialized, and every nested run is visible, the moment the build finishes.

Each nested run is created waiting on its container job (WAIT_CONTAINER_JOB). It exists and can be inspected, but nothing in it starts until the container job that owns it actually runs.

When the container job then runs, the platform:

  1. Adopts the nested run built for it. The build already created it, so the container attaches to that run rather than building a second one. The same adoption covers a nested workflow that hasn't reached a final state after a recurrence or a service restart.
  2. Builds the nested workflow now, if it doesn't have one. A container built before build-time expansion existed, or one whose child is missing, falls back to building at dispatch — checking the nesting depth (default 10 levels), resolving the reference against the workflows deployed for the run's schedule date, and checking for a circular reference.
  3. Moves the container job to running.

The container job remains running until the nested workflow completes, then settles to the matching outcome.

A running container does not occupy a job slot. The environment has a cap on how many jobs may be running at once (100 by default), and a container sits in running for as long as its nested workflow does — which can be hours — while holding no agent capacity of its own. Counting it against the cap deadlocked the environment: once enough containers were running to fill it, no slot was left for their own nested jobs, so the children waited to start indefinitely, the containers never finished, and nothing else in the environment could dispatch either. Running containers are now excluded from that count, as they are in Classic. A container waiting to start still takes an ordinary place in the queue, and a null job still counts — it is only running for a moment.

What the build reports​

A build of a workflow with container jobs reports three things alongside its job counts: how many containers were expanded in this workflow, how many nested instances were created beneath it (counted through the whole hierarchy), and any container failures.

A container that can't be built doesn't fail the build​

If a container's sub-workflow can't be built — a missing or unreadable reference, nothing deployed for the schedule date, an ambiguous reference, a cycle, too deep, or nothing in it qualifying for the date (no frequency fired, or every job in it is Disabled) — the parent build still succeeds. The parent's own jobs are complete and runnable, so destroying them because one child couldn't be built would be the wrong trade. Instead:

  • The reason is written to the container job's termination description, so it's visible on the job.
  • The container job is listed in the build result's container failures, naming the job, the workflow instance that owns it, the workflow it referenced, and the reason.

A grandchild's failure is reported the same way, in the same list, because nothing else surfaces a nested build's result to you. The list is tree-wide, which is why each entry names its own owning instance: a container job name is unique only within its own workflow, so the job name alone wouldn't say which schedule to go and look at. The owner is named with the same composed Workflow_Instance$nnnn convention the Processes grids use, so two fan-out instances of one workflow that failed the same container are told apart, and so is a repeat build of one instance.

A container that failed to expand is not de-duplicated across fan-out instances: each instance expands on its own, so each one that came up empty is its own entry.

Where you're told a container has nothing to run​

You don't have to open the job to find out. Three surfaces report it:

A warning on the Processes page, after the build. Scheduling a workflow whose build left any container empty raises a warning banner above the list on the Processes page — not inside the Schedule Workflow dialog, which closes on a build that succeeded. It reads "Scheduled. 1 container job has no sub-schedule to run:" and lists, per container, the owning schedule instance → the container job → the sub-workflow it referenced, with the build's own reason beneath each one. It closes with what the consequence actually is:

Scheduled. 1 container job has no sub-schedule to run:
• HB-Demo-Parent → New Job → HB-Demo-Child
No deployment found for workflow 'HB-Demo-Child' on 2026-09-11

A container job with no sub-schedule fails when it starts, unless the workflow it
references is deployed for this date first.

The referenced workflow is left out of the path when the reference itself was unreadable — which is exactly the case where there's no name to report.

note

The banner has no timer: it stays until you dismiss it with its close control, because it's the only statement of which containers came up empty. The ordinary scheduling confirmation still appears alongside it when it has something to add — instances that failed or were skipped, and instances an overwrite deleted, none of which the banner covers. When it would add nothing, the banner is shown on its own.

A marker on the container job, for as long as the warning is still true. The container job carries a warning icon — beside its status badge on the workflow diagram, and beside its name in the Job View list. Hovering it shows the build's reason. It's on the job itself, so it covers a nested level too: each nested instance's own diagram and list mark their own containers, which the one-shot build response can't do.

The marker is shown only when all of these hold, so it can't contradict what the job's badge says:

  • the job is a Workflow Container;
  • it carries a termination description from the build;
  • its status is one where that reason is still true of the job — any waiting status, ON_HOLD, FAILED, FAILED_PENDING_TERM or INITIALIZATION_ERROR.

The job's own termination description, on its detail panel, which is where the reason has always been.

Good to know

The markers earn their keep on a future-dated build, where the container sits in WAIT_START_TIME and nothing else says anything. On a same-day build with no start-time constraint the container dispatches within about a second and fails, and the dispatch error replaces the build's reason — at which point the red FAILED badge is the signal.

Resolving a container clears its build warning​

Cancel, Mark Finished OK and Skip clear the build's reason from a container job they resolve, so the marker goes with the action rather than standing on a job you've closed out.

ActionClears the build's reason
Cancel, Mark Finished OKWhen the container was still waiting — any waiting status, or ON_HOLD.
SkipAlways, when the skip defers to the engine (the job goes to JOB_TO_BE_SKIPPED). On a terminal skip, the waiting rule above applies.
RestartAlways, as it always has.
Mark FailedNever — it writes your own reason into the same field, and the job leaves the statuses the marker reads.

The reason is deliberately kept when the container is resolved from FAILED, INITIALIZATION_ERROR or MARKED_FAILED: the text there is the engine's account of a real dispatch failure, or your own, and clearing it would erase it.

A container left at JOB_TO_BE_SKIPPED by its frequency — rather than by an operator — keeps its marker, because it really does have no sub-schedule to run.

Two limits bound one build​

Depth has always been capped. Build-time expansion adds two more bounds, because depth alone doesn't limit how much work one build does:

LimitDefaultWhat happens when it's reached
Sub-workflow instances created across the whole hierarchy250The next container is recorded as a failure naming the limit. Everything already built stands.
Wall-clock budget for expansion25 secondsSame — the next container is recorded as a failure naming the deadline, and the build returns.
Nesting depth10 levelsThe container fails with the depth error.

Both new limits are deliberately reported, not silent: the build succeeds, and the containers that weren't expanded say so. Containers that failed to build anything don't consume the instance budget, so one bad reference can't push a later valid container over a limit nothing really reached.

Deleting or rebuilding a parent​

Deleting a workflow instance, or rebuilding it with overwrite, cascades to its whole container subtree — every nested run beneath it, at every level, goes with it. A nested run with jobs still running is left alone rather than cancelled out from under an agent, and the cascade stops there rather than descending past it.

A nested run whose container can never run​

A container job that goes terminal without ever dispatching — cancelled, or failed on a dependency — would leave its nested run waiting forever. Such a run is now reaped: the platform notices the container can no longer drive it and cancels it, both as it happens and in a pass at startup that collects any that predate this. A nested run with jobs still running is left alone.

How a nested run is named​

A nested run is named for its place in the hierarchy, so two runs of the same sub-workflow under different containers are told apart:

  • Its full name is <parent's full name>_<container job name>[<sub-workflow name>]. This is recursive, so a three-level hierarchy reads Top_RunMiddle[Middle]_RunLeaf[Leaf].
  • Its hierarchy path is the chain of container jobs that leads to it, separated by \ — Top\RunMiddle\RunLeaf. It names the jobs rather than the workflows, so it stays short and reads as a location.

Both are derived from the sub-workflow's resolved name, so a container pointing at a renamed workflow is named for what the workflow is called now, not for the spelling stored on the reference.

A full name is fitted to 255 characters by shortening the prefix, never the sub-workflow name — the sub-workflow name is what identifies which child this is, and the prefix is context. A shortened prefix ends in a checksum of what was dropped, so two long siblings that agree on their opening characters don't collapse into one name.

note

Neither value replaces the workflow's own name: a nested run still records the sub-workflow's canonical name too, which is what cross-workflow dependencies and child reuse match on. Top-level runs have no full name or hierarchy path — their name is just the workflow's name.

How the reference is resolved​

The reference is resolved against the deployments effective for the schedule date the parent is running for — not against the list of workflows that exist. A workflow that exists but isn't deployed for that date is just as unbuildable as one that was never created, and the error says so by naming the date.

Within that set:

  • If the reference carries workflow.id, the id decides. The stored name is not consulted, so a workflow that has been renamed since the container was authored still resolves.
  • If it carries only a name, the name is matched case-insensitively and ignoring surrounding whitespace. A case-only rename of the target therefore doesn't break a container that still spells the name the old way.

The reference is read from the container job instance's own configuration, so a container that was fanned out by a multi-instance group or an agent pool — or whose job name was edited after the build — resolves its target the same way a plain one does.

Good to know

The check that runs before the build and the build itself resolve the reference by the same rule, so a reference the pre-check accepts is one the build can find.

Every one of these messages names the workflow, in quotes, and never its identifier — in the build's container failures, in the container job's termination description, in the markers' tooltips, and in the error a container fails with at dispatch. A reference that carries an id used to be reported by that id, which told you nothing you could search for. The name is always available, so there's no case where the message has nothing to say.

Circular references and depth​

The cycle check walks the container's full chain of ancestors, comparing by id when the reference has one and by normalized name otherwise. That catches indirect cycles — A embeds B, which embeds A — not only a workflow embedding itself, and it isn't defeated by a rename or a difference in capitalization. The depth limit is a separate backstop for genuinely deep nesting.

When the platform can't answer yet​

A container distinguishes "this reference is wrong" from "the platform couldn't tell me":

  • A configuration problem — no such deployment for the date, a cycle, too deep — fails the job.
  • A transient platform condition — a service that didn't answer, a database failover, or another builder already partway through building the same nested run — defers the job. The container keeps the status it had, releases any resources it had acquired so other jobs aren't blocked behind it, and tries again on the next scheduling pass.

A deferring container isn't silent: it keeps its planned start time, so once that passes it surfaces as Late to start like any other job that hasn't started on time.

Recurring containers​

When a container job is set to recur, the nested workflow is recycled in place rather than rebuilt — the same nested instance runs again for each recurrence. Two consequences are worth knowing:

  • The nested workflow fires its completion events on every recurrence, not just the first.
  • Each recycle is a workflow status transition on the nested run, from completed back to waiting on its container, so a notification trigger that matches on that transition sees each one.

After a service restart​

If the platform restarts while a container is running, the container is reconnected to its nested run on reload. A nested workflow that reached a final state during the outage is reconciled and handed to the same completion path a live finish takes, so the container settles rather than staying running against a run that already finished.

How a nested run relates to the rest of the schedule​

A container's nested workflow is a private sub-schedule, not another run on the daily schedule, and the platform keeps the two apart:

  • Schedule-level events skip it. $SCHEDULE:HOLD, $SCHEDULE:RELEASE, $SCHEDULE:CANCEL and $JOB:ADD resolve to the standalone run you named, never to a container's nested run. If a workflow name and date exist only as a container's nested run, the event reports that the target wasn't found — which is the safer answer than quietly acting on a container's child and breaking its roll-up.
  • Cross-workflow dependencies prefer the standalone run. If a workflow is both on the daily schedule and embedded by a container on the same date, a dependency in another workflow resolves to the standalone instance.
note

One gap remains: a job-level event naming a workflow that exists only as a container's nested run can still resolve into it, because job-level targets don't yet carry the marker the schedule-level ones filter on.

Outcomes​

ResultStatusWhen
Nested run adoptedRunningThe nested workflow was built with the parent, and the container attaches to it. This is the normal case.
Nested workflow building/runningRunningReference resolved and the nested workflow built at dispatch — a container that predates build-time expansion, or one repairing a missing child.
Existing nested run adoptedRunningThe container already had a live nested run — after a recurrence or a restart.
Never dispatched, nested run reapedTerminal (as set)The container went terminal without dispatching, so its nested run was cancelled rather than left waiting.
Deferred, retried next passUnchangedA service didn't answer, or another builder holds the same nested run.
Failed — missing referenceFailedThe job carries no workflow name.
Failed — no deployment for the dateFailedNothing deployed for the schedule date matches the reference. The error names the date.
Failed — ambiguous referenceFailedMore than one deployment matches the reference.
Failed — circular referenceFailedThe referenced workflow is already an ancestor (the error names the cycle path).
Failed — nesting too deepFailedThe nesting depth limit (default 10) was exceeded.

Troubleshooting​

SymptomLikely causeResolution
"No deployment found for workflow … on …" (the message names the schedule date)The target isn't deployed for that schedule date, or the name/id is wrongDeploy the target workflow so it's effective for that date, or correct the reference (Builder).
"Ambiguous workflow reference: … deployments match"More than one deployment answers the referencePoint the container at a specific workflow with the picker, so it stores the id (Builder).
"Circular workflow reference detected"The embedded workflow references one of its own ancestorsRemove the cycle; an embedded workflow can't include a parent (Builder).
"Maximum nested workflow depth … exceeded"Containers nested more than the limit (default 10)Flatten the design so nesting stays within the limit (Builder).
Container job stays running for a long timeThe nested workflow hasn't finishedOpen the nested workflow instance and troubleshoot it there (Operator).
Container sits in its pre-run status across several passes, then goes Late to startIt's deferring on a transient condition rather than failingCheck whether the platform's services are healthy; the container retries on its own once they are (Operator).
"Workflow-container job missing workflow reference"The job has no workflow selectedSet the workflow reference on the job (Builder).
The build succeeded, but a warning banner says a container job has no sub-schedule to runThe container couldn't be expanded at build. The banner names the owning instance, the job and the reason; the same reason is on the job and behind its warning iconFix what the reason names — most often, deploy the referenced workflow for that date — then rebuild with overwrite (Builder/Operator).
A container job carries a warning icon beside its name or status badgeIts sub-schedule couldn't be built, and the job is still waiting, held or failedHover the icon for the reason. Resolve the reference and rebuild, or Cancel, Skip or Mark Finished OK the container, which clears the warning (Operator).
"Build-time expansion limit of … sub-workflow instances reached for this build"The hierarchy is larger than one build's instance capSplit the design, or raise the cap with your administrator (Builder).
"Build-time expansion deadline of …ms reached for this build"Expansion ran out of its time budget before reaching this containerRebuild — the containers already expanded are kept; or split the design so one build does less (Operator).
"This workflow is marked as a sub-workflow and can only be run by a container job"A direct build was attempted on a workflow marked Sub-WorkflowBuild the parent workflow that contains the container, or clear the flag if the workflow is meant to run on its own (Builder).
A workflow you want to embed isn't in the container's pickerThe picker lists sub-workflows onlyMark the target workflow Sub-Workflow on its Overview panel (Builder).
"Job task definition not found" / "… carries no parameters"The job instance's stored configuration is missing or unusableRebuild the workflow, or set the reference on the instance (Builder).
A hold/release/cancel event "didn't find" the workflowThe only instance for that name and date is a container's nested runTarget the standalone run, or act on the container's parent workflow instead (Operator).

Contact support when​

  • The referenced workflow exists, is deployable, isn't circular, and is within the depth limit, but the container still fails to build it.
  • A container reports its nested workflow finished, but its own status doesn't settle to match.

Include the parent workflow instance, the container job name, the referenced workflow name/ID, and the nested workflow instance ID if one was created.