windmill-labs/windmill · warning
Skipping ${cyclic.length} script(s) on a dependency cycle: $
Error message
Skipping ${cyclic.length} script(s) on a dependency cycle: ${cyclic.sort().join(", ")} What it means
During `wmill pipeline run`, topoOrder orders the selected scripts; any scripts that form a dependency cycle cannot be ordered and are excluded from the run, with this warning listing them. Running a cyclic subset is impossible, so the CLI drops those nodes rather than deadlocking.
Source
Thrown at cli/src/commands/pipeline/pipeline.ts:873
if (reachable.has(e)) {
reachableEnds.push(e);
} else {
droppedEnds.push(e);
log.warn(
`end '${idLabel(e)}' is only reachable through a skipped input/event handler — not run (bind it with --upload to include it).`,
);
}
}
}
// Selections can still pull a non-runnable node in via graph reachability (e.g.
// whole-pipeline mode collects every root's closure) — drop macro libraries and
// local-only display nodes before ordering so they never run.
for (const p of notRunnablePaths) selectedScripts.delete(p);
const { order, cyclic } = topoOrder(graph, selectedScripts);
if (cyclic.length > 0) {
log.warn(`Skipping ${cyclic.length} script(s) on a dependency cycle: ${cyclic.sort().join(", ")}`);
}
// `// partitioned` scripts need a resolved `partition` arg. Deployed runs get
// it from backend run-start resolution, but previews (`--local`) never do —
// so resolve it client-side there: `--partition` wins; otherwise time kinds
// default to the current UTC period (mirroring the backend defaults; custom
// `tz=`/`format=`/`start=` opts are a backend concern — pass --partition
// explicitly to match them). `dynamic` has no default. For deployed runs the
// arg is only injected when `--partition` is given (an explicit backfill).
const partitionKindByPath = new Map(
graph.runnables
.filter((r) => r.usage_kind === "script" && r.partition_kind)
.map((r) => [r.path, r.partition_kind!]),
);
const partitionValueFor = (nodePath: string): string | undefined => {
const kind = partitionKindByPath.get(nodePath);
if (!kind) return undefined;
if (opts.partition) return opts.partition;View on GitHub (pinned to e474e8803c)
Solutions
- Open the pipeline in the UI and remove/reverse the dependency edge that closes the cycle.
- Check the listed paths (the warning sorts them) and trace their mutual dependencies.
- Re-run once the DAG is acyclic — the skipped scripts will then be included.
- If the cycle is intentional display-only wiring, keep it out of the runnable selection (macro libraries/local display nodes are normally filtered).
Example fix
// before: a depends on b, b depends on a -> both skipped // after (edit pipeline definition): remove a's dependency on b // a: depends: [b] -> a: depends: []
Defensive patterns
Strategy: validation
Validate before calling
// detect cycles in the dependency graph before running
function hasCycle(nodes) {
const state = {};
function visit(n) {
if (state[n] === 1) return true;
if (state[n] === 2) return false;
state[n] = 1;
for (const d of depsOf(n)) if (visit(d)) return true;
state[n] = 2;
return false;
}
return Object.keys(nodes).some(visit);
} Prevention
- Review dependency edges when editing the flow; avoid mutual references.
- Run the whole pipeline once after structural edits to surface cycles early.
- Treat the sorted list in this warning as the exact nodes needing an edge removed.
- Keep macro/display-only wiring out of runnable script dependencies.
When it happens
Trigger: The selected window of the pipeline DAG contains a cycle (script A depends on B which depends on A), e.g. after a misconfigured dependency edge or a hand-edited pipeline definition.
Common situations: Accidentally adding a dependency edge backwards while editing the flow; importing a pipeline whose generated dependencies form a loop; macro/library scripts referencing each other.
Related errors
- end '${idLabel(d)}' is not downstream of the start — ignored
- end '${idLabel(e)}' is only reachable through a skipped inpu
- --upload for ${p} is unused — it isn't in the run selection.
- --arg for ${p} is unused — it isn't in the run selection.
- App ${appPath} not found
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/954baa6c1a5090bc.
Report an issue: GitHub.