windmill-labs/windmill · error · Error

--arg binds ${p}:${b.param} more than once.

Error message

--arg binds ${p}:${b.param} more than once.

What it means

Duplicate-binding guard for --arg in the pipeline run command: two or more --arg <script>:<param>=<value> bindings target the same script and param, so one would silently overwrite the other (later spreads win). The inputs at fault are the repeated --arg entries for that script:param pair.

Source

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

    const binding = parseUploadBinding(spec);
    const id = resolveScriptTokenOrThrow(binding.scriptTok, "--upload");
    const p = scriptPathOf(id);
    (boundBindingsByPath.get(p) ?? boundBindingsByPath.set(p, []).get(p)!).push(binding);
    boundNodeIds.add(id);
  }

  // `--arg <script>:<param>=<value>` overlays a plain run arg on a script in
  // the selection. Unlike `--upload` it does not make the script a runnable
  // start — it only supplies a value if the script runs.
  const plainArgsByPath = new Map<string, Record<string, unknown>>();
  for (const spec of opts.arg ?? []) {
    const b = parseArgBinding(spec);
    const p = scriptPathOf(resolveScriptTokenOrThrow(b.scriptTok, "--arg"));
    const merged = plainArgsByPath.get(p) ?? plainArgsByPath.set(p, {}).get(p)!;
    // Object.hasOwn, not `in`: a param named like a prototype member
    // (`toString`, `constructor`, …) must not trip the duplicate check.
    if (Object.hasOwn(merged, b.param)) {
      throw new Error(`--arg binds ${p}:${b.param} more than once.`);
    }
    merged[b.param] = b.value;
  }

  // `// macros` libraries are definition-only: their macros are injected into
  // consuming DuckDB scripts at run time, so "running" one is a no-op preview.
  // They are excluded from starts and from every selection below (they'd
  // otherwise read as manual roots, since nothing triggers them).
  const macroLibPaths = new Set(
    graph.runnables
      .filter((r) => r.usage_kind === "script" && (r.macros?.length ?? 0) > 0)
      .map((r) => r.path),
  );
  // Non-runnable graph nodes: the macro libraries above, plus (under `--local`)
  // any script node with no local file. The local graph surfaces macro-consumer
  // nodes for lineage display — a DuckDB script that calls a macro but isn't a
  // `// pipeline` member (so has no previewable content). They must never enter
  // the run: as a manual root they'd be scheduled, and a preview would fail with

View on GitHub (pinned to e474e8803c)

Solutions

  1. Keep only one --arg per script:param pair
  2. If two different params were intended, correct the param name in one binding
  3. If you need a composite value, pass it as one JSON value: --arg script:param='{\"a\":1}'
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at cli/src/commands/pipeline/pipeline.ts:672 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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