windmill-labs/windmill · warning

end '${idLabel(e)}' is only reachable through a skipped inpu

Error message

end '${idLabel(e)}' is only reachable through a skipped input/event handler — not run (bind it with --upload to include it).

What it means

In `wmill pipeline run`, an --end that IS topologically downstream of --start can still be unreachable at runtime because the only path to it passes through an input/event handler that is skipped in CLI runs. Such ends are dropped from reachableEnds and this warning explains why, suggesting --upload to bind the handler and include it.

Source

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

    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).`,
        );
      }
    }
  }

  // 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 —

View on GitHub (pinned to e474e8803c)

Solutions

  1. Add --upload <handler-path>=<file/value> to bind the skipped input/event handler so its branch runs.
  2. Choose a different --end that doesn't depend on a skipped handler.
  3. Run the full pipeline segment in the UI (where handlers execute) if handler execution is required.
  4. Restructure the flow if this end should be runnable standalone without the handler.

Example fix

// before: end only reachable via skipped handler
wmill pipeline run f/myflow --start preprocess --end report
// after
wmill pipeline run f/myflow --start preprocess --end report --upload f/myflow/input=data.csv
Defensive patterns

Strategy: validation

Validate before calling

// ensure ends don't depend on skipped handlers: inspect the path between start and end
// if any intermediate step is an input/event handler, add a --upload binding up front
const needsUpload = ['f/myflow/input']; // handler steps on the path
const uploadArgs = needsUpload.map(h => `--upload ${h}=<value>`).join(' ');

Prevention

When it happens

Trigger: Running with --end pointing at a node whose only incoming path runs through an input/event handler step (which doesn't execute in a local/CLI run unless bound via --upload).

Common situations: Selecting an end node that hangs off an event-triggered branch; trying to run a downstream node whose dependency is an upload-style input handler; forgetting that CLI runs skip input/event handlers by default.

Related errors


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