Files
windmill/cli
hugocasaandClaude Opus 5 d54a66f15c fix(cli): say where a sync push deleted variable or resource went (#10851)
* fix(cli): keep variables and resources a sync push repo never tracked

`wmill sync push` archives a script it no longer finds locally, but hard-deletes
a variable or a resource: the credentials go for good. A remote-only one is
equally a deletion being deployed and one the repository never had, provisioned
on the instance or written by a script at runtime, and reading the second as a
deletion is unrecoverable.

Committed history tells them apart. A push whose changeset deletes a variable or
resource now asks what this branch has ever tracked at `*.variable.*` /
`*.resource.*`; anything it has never recorded is kept on the remote (prompted
for on a TTY), and a real deletion, recorded before the commit that removed it,
still applies. Where the history cannot be read (shallow clone, sparse checkout,
no repository) there is no evidence either way, so the deletion stands as before
with a warning naming the remedy — the git-sync "Pull from repo" job runs in a
depth-1 clone and must keep deploying the deletions it always has.

`--delete-untracked-secrets` / `deleteUntrackedSecrets` opts a mirror-semantics
pipeline back into deleting them unattended.

Fixes GIT-980

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): classify secret-bearing deletions the way the push itself does

Three ways the suffix match missed:

- A fileset child can be any file, `inner.resource.yaml` included, and its
  deletion re-pushes the parent rather than deleting anything. Classifying with
  the push's own `getTypeStrFromPath`, behind the same fileset exclusion the
  apply loop uses, keeps the two in step.
- Deleting `f/x.resource.file.ini` deletes the resource `f/x` outright, so
  without that file in the pathspecs every file resource walked past the check.
  Its two files now count as the one resource they delete.
- A `specificItems` item is committed as `y.<workspace>.variable.yaml` while
  `elementsToMap` collapses it to the base path the changeset carries, so a
  deletion the user did commit read as never tracked. The history is searched
  under both names.

`gitRecordedPaths` also reads its history with `core.quotePath=false`: a path
with a non-ASCII byte came back C-quoted and matched nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): judge held-back deletions per object, not per file

`DELETE /variables/delete` takes the resource at the same path down with it, and
`DELETE /resources/delete` does the same to the variables its value references,
so a tracked deletion could destroy an untracked object the push had just
reported it was keeping. A file resource had the same shape from the other end:
two files for one resource, either survivor deleting it.

The unit is the server-side object. One file left unaccounted for by history now
holds the whole object back, so nothing in a group reported as kept is deleted.
The residual is a resource whose value references a variable at another path,
which stays possible and is called out in the PR.

Also corrects what the messages claim. Deleting a variable or resource is not
irrecoverable: both move to the workspace trash, which keeps them for three days
(migrations/20260326000000_trashbin.up.sql, CE since v1.665.0). The asymmetry
with a script is real but narrower, and the prompt defaults to No, so it should
say what it actually costs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): warn when sync push deletes variables the repo never tracked

`sync push` deletes a remote variable or resource that has no local file. An
object the repository has never tracked was provisioned outside it, by hand or
by a script at runtime, rather than deleted from it, and the change list said
nothing to tell the two apart.

The push still deploys every deletion — that is what the repo-is-the-mirror
contract means, and a shallow clone (the git-sync "Pull from repo" job, a
default actions/checkout) could not tell them apart anyway. What changes is that
the preview names the ones this branch's history has no record of, before the
prompt that confirms them, and points at the excludes that stop them recurring.

Deleting is also not final, which the CLI was alone in not saying: both handlers
move the item to the workspace trash first, restorable for three days
(migrations/20260326000000_trashbin.up.sql, CE since v1.665.0). A push that
deleted any now says so.

This replaces the earlier hold-back design. Keeping objects back changed what a
push deploys, needed a flag and a wmill.yaml key to opt out of, and could claim
to keep an object that a linked deletion then cascaded onto. Reporting cannot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): name the paths the untracked-deletion warning is about

A push can delete a tracked and a never-tracked resource together, where "1
resource" identified neither. The warning lists the paths instead of counting
them, so the reader knows which one to exclude.

Outside a git checkout it no longer opens "This branch's history", which
contradicted the reason it went on to give, and it drops the pronouns that
disagreed with a plural count. The history walk is skipped under --json-output,
where both notices are silenced and its result had no reader.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): vouch for an object with any file in history, not just deleted ones

The tracked set was built from the deletions being judged, so a companion file
the push was not deleting could not vouch for its object: a file resource whose
`.resource.yaml` stays while its content file goes was reported as never
tracked, though the repository plainly owned it. It is built from the whole
history now, which also turns the workspace-specific lookup around — history is
normalized to base paths, the form the changeset already carries, instead of
each candidate being searched for under two names.

The warning also prints one line per server-side object rather than per file, so
a file resource is the one deletion it is rather than two.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): name both kinds at a shared path, gate the history path conversion

Three from review:

A variable and a resource at one path are judged together, since deleting either
takes both, but they are two objects to name — keying the printed lines by path
alone dropped one of them.

`fromWorkspaceSpecificPath` strips a `.<workspace>` segment wherever it finds
one, so a history entry that merely looks workspace-suffixed was re-keyed onto a
different object, whose history then vouched for it. Only a path `specificItems`
claims is converted now. Not reachable from a server object (the backend rejects
`.` in paths), but history holds whatever was committed.

`secretBearingKey` lost its last caller when the tracked set moved to object
paths; removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): identify a secret-bearing object by kind as well as path

A variable and a resource can share a path and are still two backend objects:
`DELETE /variables/delete` drops the same-path resource unconditionally, while
`DELETE /resources/delete` drops only the variables its value references. Keying
tracked history by path alone let a committed variable vouch for a resource the
repository never had, which then went unmentioned. The cascade is a reason to
report both, not to treat them as one. A file resource's two files keep one id.

`git log HEAD` also fails on a repository with no commits, which was reported as
"its history could not be read. Check that git runs correctly in this directory"
— true of neither.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(cli): tighten the comments on the untracked-deletion warning

Halves the prose without dropping a constraint: the trashbin retention is
stated where the message says it rather than twice more in doc comments, and the
two stacked comments at the print site had come to contradict each other, one
still describing a same-path variable and resource as judged together.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): say where a deleted variable or resource went

`sync push` deletes a remote variable or resource that has no local file, and
said nothing more. Both handlers move the item to the workspace trash first,
restorable for three days (migrations/20260326000000_trashbin.up.sql, CE since
v1.665.0), and the CLI was the one surface never to mention it — the report
behind this concluded the deletion was final and there was nothing to restore.

A push that deleted any now ends with where they went and how long they have.

Drops the untracked-deletion warning this branch carried: distinguishing a
deletion the repo deployed from an object it never owned needs the branch's git
history, and roughly 130 lines to read it and be right about the answer, for a
claim the trash already softens. Two fixes it turned up in the data-table
migration guard, which reads history the same way, are kept: a path with a
non-ASCII byte came back C-quoted and matched nothing, and a repository with no
commits was reported as one where git does not run.

Fixes GIT-980

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): name the trashbin correctly and say who can restore

The tab is labelled Trashbin, not Trash, and `restore_trash_item` requires
admin, so a non-admin reading the old line would go looking for a control they
do not have.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(cli): move the migration-guard git fixes to their own PR

They fix `gitRecordedDatatableMigrationPaths`, which this PR no longer touches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): don't count a .lock deletion the push skips

The apply loop `continue`s past a non-raw-app, non-dbt `.lock` deletion before
reaching the delete switch, so nothing happens on the server. The classifier did
not mirror that, and `f/x.resource.file.lock` reaches it as a resource through
`isFileResource` — a resource type whose format_extension is literally `lock`
would have the notice announce a deletion the push never performed.

A raw-app or dbt `.lock`, the two that loop does not skip, classifies as its
bundle's own kind well before the file-resource check, so a suffix test is
enough.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(cli): state what the classification tests protect

The header described the change rather than the invariant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:07:33 +02:00
..
2022-11-01 15:53:28 +01:00
2023-02-03 19:49:46 +01:00
2026-02-23 09:09:16 +00:00
2025-12-26 20:32:42 +00:00

Windmill CLI

A simple CLI allowing interactions with windmill from the command line.

You can find more information in Windmill Docs

Installation

Install the wmill CLI tool using npm install -g windmill-cli.

Update to the latest version using wmill upgrade.

Workspaces

To get started run wmill workspace add or use the instructions from the workspace settings.

Running Flows & Scripts

Run a script or flow using wmill flow/script run u/username/path/to/script and pass any inputs using --data + Inputs specified as a JSON string or a file using @ <filename> or stdin using @-.

Curl-style syntax using -d @- for stdin or -d @<filename> is also supported.

Flow Steps and Logs will be streamed during execution automatically.

CLI input example

Pushing Resources, Scripts & More

The CLI can push specifications to a windmill instance. See the examples/ folder for formats.

Switch to a different workspace

wmill workspace switch <workspace_name>

Sync a workspace

Pull

wmill sync pull

Push

wmill sync push

We recommend using the --yaml option to use yaml instead of json as the encoding format. Yaml will be made the default soon.

Pushing individual files

You can push individual resources using wmill <type> push <file_name> \<remote_name\>. This does not require a special folder layout or file name, as this is given at runtime.

Listing

All commands support listing by just not providing a subcommand, ie wmill script will result in a list of scripts. Some allow additional options, learn about this by specifying --help.

User Management

You can add & remove users via wmill user add/remove, and list them using wmill user

Pulling

You can pull the entire workspace using wmill pull

Completion

The CLI comes with completions out of the box via wmill completions <shell>. (Via cliffy)

Bash

To enable bash completions add the following line to your ~/.bashrc:

source <(wmill completions bash)

Fish

To enable fish completions add the following line to your ~/.config/fish/config.fish:

source (wmill completions fish | psub)

Zsh

To enable zsh completions add the following line to your ~/.zshrc:

source <(wmill completions zsh)

Development

AI Guidance Variants

wmill init can now materialize alternate AI guidance bundles without changing the generated defaults in the repo, but this is exposed as internal env-var overrides rather than public CLI flags.

Examples:

WMILL_INIT_AI_SKILLS_SOURCE=/path/to/custom/skills wmill init --use-default
WMILL_INIT_AI_SKILLS_SOURCE=/path/to/custom/skills WMILL_INIT_AI_AGENTS_SOURCE=/path/to/AGENTS.md wmill init --use-default
WMILL_INIT_AI_SKILLS_SOURCE=/path/to/custom/skills WMILL_INIT_AI_CLAUDE_SOURCE=/path/to/CLAUDE.md wmill init --use-default

This is the same guidance-writing path used by the benchmark CLI under ai_evals/, so the benchmark harness and wmill init now generate the same project guidance shape:

  • AGENTS.md
  • CLAUDE.md
  • .agents/skills/*
  • .claude/skills/*

windmill-yaml-validator

wmill lint imports the sibling windmill-yaml-validator package from source rather than from npm, so its schemas always match the OpenAPI specs of the current checkout. bun install regenerates them through this package's preinstall script; run it again after editing openflow.openapi.yaml or backend/windmill-api/openapi.yaml:

npm --prefix ../windmill-yaml-validator run gen

Running Tests

Prerequisites:

  • PostgreSQL running locally (default: postgres://postgres:changeme@localhost:5432)
  • Rust toolchain installed

Run tests locally (full features):

bun test test/

Run tests in CI mode (minimal features, skips EE tests):

CI_MINIMAL_FEATURES=true bun test test/
Variable Description
CI_MINIMAL_FEATURES Set to true to skip EE-dependent tests
DATABASE_URL PostgreSQL connection string
EE_LICENSE_KEY Enterprise license key for EE features