windmill-labs/windmill · warning

end '${idLabel(d)}' is not downstream of the start — ignored

Error message

end '${idLabel(d)}' is not downstream of the start — ignored.

What it means

In `wmill pipeline run`, when an --end target is not downstream of the resolved --start node, boundedSet drops it and this warning names each dropped end. The run still proceeds with the reachable window; the unreachable --end is simply ignored.

Source

Thrown at cli/src/commands/pipeline/pipeline.ts:843

    ),
  );
  let selectedScripts: Set<string>;
  let reachableEnds: string[] = [];
  let droppedEnds: string[] = [];
  if (runAll) {
    // Whole pipeline = everything reachable from the valid starts (schedule/manual
    // roots), cutting at the barriers above. A node reachable via another,
    // non-barrier path still runs.
    selectedScripts = new Set(scriptsOf(reachableCutting(dag, starts, barriers)));
  } else if (ends.length === 0) {
    // No bound → full read-aware downstream of start (pure readers included),
    // still cut at barriers so an event/upload descendant isn't run empty.
    selectedScripts = new Set(scriptsOf(reachableCutting(dag, [start!], barriers)));
  } else {
    const res = boundedSet(dag, start!, ends);
    droppedEnds = res.droppedEnds;
    for (const d of droppedEnds) {
      log.warn(`end '${idLabel(d)}' is not downstream of the start — ignored.`);
    }
    // Intersect the bounded window with the barrier-cut closure from the start so
    // a barrier (or a node only reachable through one) inside the window is dropped.
    const reachable = reachableCutting(dag, [start!], barriers);
    selectedScripts = new Set(
      scriptsOf([...res.nodes].filter((n) => reachable.has(n))),
    );
    // An end that boundedSet reached but the barrier cut removed is NOT satisfied:
    // report it as dropped (not reachable) and warn, so `--json`/the plan don't
    // claim a bound was met when its only path ran through a skipped handler.
    for (const e of res.reachableEnds) {
      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).`,
        );

View on GitHub (pinned to e474e8803c)

Solutions

  1. Fix the --end value to a path/id that is downstream of --start (check the pipeline graph in the UI).
  2. Remove the invalid --end and rerun without it.
  3. Verify the script path exists and wasn't renamed (wmill pipeline script list or the UI).
  4. If you meant a different starting point, adjust --start so the end is actually reachable.

Example fix

// before
wmill pipeline run f/myflow --start preprocess --end trainning  # typo, not downstream
// after
wmill pipeline run f/myflow --start preprocess --end training
Defensive patterns

Strategy: validation

Validate before calling

// verify ends are downstream of start using the plan output before running
const plan = JSON.parse(execSync(`wmill pipeline run f/x --start ${start} --end ${end} --json --dry-run 2>/dev/null`).toString());
if (plan.droppedEnds?.length) throw new Error(`invalid --end: ${plan.droppedEnds.join(',')}`);

Prevention

When it happens

Trigger: Running a sub-window of a pipeline with --start X --end Y where Y is not reachable from X in the pipeline DAG (typo in the end path/name, or ends that sit on a parallel branch).

Common situations: Misspelled or stale script path passed to --end after refactoring the flow; assuming --end accepts multiple arbitrary nodes rather than descendants of --start; copy-pasting ends from a different pipeline.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/85cd2c1ad3e92c3d. Report an issue: GitHub.