* feat: add wmill protection-rules pull/push CLI commands
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: use directional keys for protection-rules pull --json diff
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address review — exit non-zero on failure, resolve override workspace key
- failure paths in pull/push now exit 1 so CI/scripts detect failed reconciles
- --override writes under the resolved workspace key (findWorkspaceByGitBranch),
not the raw branch, so gitBranch-mapped entries aren't left inert
- pull --replace clears a shadowing protectionRules override so top-level takes
effect (was an infinite pull --diff loop)
- push reports applied create/update/delete counts on partial failure and warns
loudly when an empty list would wipe all backend rules
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address review — dry-run pull --diff no longer writes; --promotion coherent
- pull --diff returns before the no-wmill.yaml bootstrap, so a dry run never
creates/mutates wmill.yaml
- pull --promotion now writes/clears the promotion target's promotionOverrides
(the same block getEffectiveSettings reads), instead of the current branch's
regular overrides — read and write are now coherent
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: move protection rules to a per-workspace protection-rules.yaml
Replaces the wmill.yaml/SyncOptions integration (top-level + overrides +
promotionOverrides) with a dedicated protection-rules.yaml keyed by workspace
name. This removes the getEffectiveSettings layering that caused the override
shadowing / promotion-coherence / dry-run bugs entirely.
- protection-rules.yaml: { <workspace>: ProtectionRuleEntry[] }, keys must
match wmill.yaml 'workspaces' (source of truth for backend id/baseUrl/token)
- commands reduced to: pull/push [workspace] | --all, with --dry-run
- per-workspace auth resolved via tryResolveBranchWorkspace + setClient
- push remains a full reconcile (create/update/delete) with delete confirm,
empty-list wipe warning, partial-failure reporting, non-zero exit on failure
- conf.ts reverted to main; SyncOptions no longer carries protectionRules
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address review — honor explicit --base-url/--token in protection-rules
configureClientForWorkspace bypassed the credential precedence other commands
use: explicit --base-url/--token now work for stateless CI (no stored profile
or wmill.yaml baseUrl needed), and an explicit --token overrides a stored
profile's token. The backend workspace id still derives from the wmill.yaml
mapping (feature invariant).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address cubic review — consistent status on partial --all failure
cubic found that pull/push reported success:true while exiting non-zero on
partial --all failures, and that the push command description was missing from
the generated CLI docs.
- pull/push now report success:false + partialFailure:true (and exit 1) when
any --all workspace fails; success:true only on full success
- .description() calls use single string literals (not + concatenation) so
system_prompts/generate.py parses them; regenerated CLI docs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address review — --json-output must emit only JSON on stdout
Codex flagged that workspace resolution (tryResolveBranchWorkspace's log.info)
and push's empty-list delete warning print to stdout before the JSON payload,
breaking machine callers. Silence human logs via log.setSilent(true) as the
first action when --json-output is set (before readConfigFile / resolution);
log.error still goes to stderr.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: add global ai mode plan
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add global ai draft mode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: scope global ai mode to scripts and flows
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: simplify global ai workspace item shape
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: split global ai write tool into per-type tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add global ai schedule and trigger workspace item tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add dev-only /global_drafts route to inspect ai draft store
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add edit_script and patch_flow_json global ai tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add deploy_workspace_item global ai tool with confirmation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: emit open-resource action card after deploy_workspace_item
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add delete_workspace_item global ai tool with confirmation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore(system_prompts): emit RESOURCES_BASE and resource/variable zod schemas
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add global ai resource and variable workspace item tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: search_resource_types uses listResourceType to avoid embedding feature dep
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "fix: search_resource_types uses listResourceType to avoid embedding feature dep"
This reverts commit 6d1d19514a.
* feat: emit open-resource action card for variable and resource deploys
* feat: add global ai raw app workspace item tools
* feat: split raw-app prompt into chat-only authoring and cli prefix
* feat: add init_app global ai tool to scaffold raw apps from templates
* fix: pass write_flow value as JSON string for gemini compat
* refactor: hoist countExactMatches and applyExactReplace to chat/shared
* refactor: extract editableFlowJson module shared with global mode
* fix(global): preserve flow schema and groups across draft and deploy
* feat: extract inline scripts from flow reads and patches in global mode
* refactor: add findAndReplace helper for match-validated text patches
* refactor: extract getInlineRunnableContent helper for app file tools
* refactor: extract assertNotGeneratedAppFile guard for /wmill.d.ts
* feat: gate global ai mode behind localStorage flag for dev rollout
* chore: bump svelte to ^5.55.5 in raw app template (sync with main)
* fix: isolate global ai draft rollout
* fix: preserve global ai deploy metadata
* fix: harden global ai draft tools
* chore: remove global ai plan doc
* fix: align raw app prompt guidance
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Run `wmill app push` from inside an app folder (e.g. `f/foo/my_app.app/`)
with no args. The local path defaults to CWD, and the remote path is
derived from CWD relative to `wmill.yaml`, with `.app`/`.raw_app`/
`__app`/`__raw_app` suffixes stripped. Either, both, or neither
positional argument can be passed.
Also resolves `file_path` against the user's original CWD before
`resolveWorkspace` may chdir to the wmill.yaml root, so a relative
`file_path` argument is interpreted from where the user invoked the
command (previously it could resolve against the wrong directory).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): bump svelte version in `wmill app new` template
The svelte5 template pinned `svelte` to `5.45.2`, but the Svelte
compiler bundled in `wmill app dev` emits `$.delegated('click', ...)`
calls. The `delegated` export was added later, so 5.45.2 doesn't have
it — esbuild warns `Import "delegated" will always be undefined`,
replaces the call with `void 0`, and the page crashes at first
event-handler bind (white screen).
Bump to `^5.55.5` so the compiler and runtime stay in sync.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): bump svelte version in raw_apps UI template
Mirror the CLI fix: the UI's `Add raw app` flow scaffolds a
package.json with `svelte: "5.45.2"`. That works today only because
the bundled rolldown worker also pins 5.45.2 — when the worker is
upgraded past 5.51.1, the compiler will emit `$.delegated()` and the
runtime won't have it, producing the same white-page crash that hit
the CLI.
5.55.5 still exports `event` (used by the current bundled compiler),
so this is forward-compatible: it works with the 5.45.2 compiler now
and won't break when the worker is upgraded.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli-tests): stabilize flow lock-gen race + Windows path
Three CLI test failures on the latest main, all flaky on CI:
1. `Mixed Case Paths: pull and push flow with capitalized folder` and
`Integration: Mixed scripts and flows with nonDottedPaths are
idempotent`: flow create/update queues an async FlowDependencies job
that fills inline-script lockfiles and rewrites flow.value. The tests
pulled/pushed before the worker finished, so dry-run idempotency saw
phantom `*.inline_script.lock` adds and `flow.yaml` edits. Added a
`waitForFlowDependencyJob` helper that polls `/flows/get` for the
latest `dependency_job` and `/jobs_u/completed/get` until it lands,
and called it after each API/CLI flow write in both tests.
2. `HEADERS env var is forwarded on every CLI fetch` (Windows-only,
added in #9075): the new test built the CLI entrypoint via
`new URL("..", import.meta.url).pathname`, which yields `/C:/...` on
Windows and `Bun.spawn` rejected before reaching the proxy, leaving
`rejectedRequests.length` at 0. Switched to
`fileURLToPath` + `node:path.join` to match `cargo_backend.ts`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli-tests): use /flows/deployment_status to actually wait for dep job
CI reviewers (Claude, Codex) flagged the prior `waitForFlowDependencyJob`
as a no-op: it read `flow.dependency_job` from `/api/w/{ws}/flows/get`,
but `Flow` / `FlowWithStarred` (backend/windmill-types/src/flows.rs:20-60)
do not include that field. The helper exited on the first iteration
without polling.
Switch to `/api/w/{ws}/flows/deployment_status/p/{path}`, which returns
`{ lock_error_logs, job_id }`. `job_id` is the FlowDependencies UUID
written into `deployment_metadata` in the same tx as the dep-job push
(backend/windmill-api-flows/src/flows.rs:660-672 and :1275-1292), so by
the time the create/update API call returns, the response carries the
latest dep-job UUID. Then poll `/jobs_u/completed/get/{job_id}` as
before. Local runtime for `mixed_case_paths.test.ts` jumps from ~9s to
~32s, confirming the helper now actually waits instead of returning
immediately. The 404 short-circuit in `sync_pull_push.test.ts` still
works — `get_deployment_status` returns 404 when the flow is absent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Several `fetch()` callers in the CLI bypassed `OpenAPI.HEADERS` and skipped
the `HEADERS` env var, causing requests to fail behind auth gateways like
Cloudflare Access (same shape as #6421):
- `pushScript()` `/scripts/create` and `/scripts/create_snapshot` — regressed
in #8936 when the call switched from `wmill.createScript()` (SDK) to a raw
`fetch` for the `skip_if_noop` query param.
- Script preview `/jobs/run/preview_bundle`.
- App dev `/jobs_u/getupdate_sse` SSE stream.
- `wmill docs` `/api/inkeep`.
All four now spread `getHeaders()` and call `detectAuthGatewayChallenge()`
so a Cloudflare/SSO challenge surfaces a clear error instead of an opaque
JSON parse failure.
Adds `test/headers_env_var.test.ts`: spins up an auth-gateway proxy that
403s requests missing `CF-Access-Client-Id` / `CF-Access-Client-Secret` and
otherwise reverse-proxies to the test backend, then runs `wmill sync push`
of a fresh script through the proxy. Negative case (no `HEADERS` env)
verifies the proxy actually gates; positive case asserts every request
including `/scripts/create` reaches the backend with the headers attached.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add workspace-specific flag for resources and variables
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove set_ws_specific endpoint and fix rust-client compilation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: fall back to workspace name for ws_specific file naming
When wsNameForFiles is not set (no wmill.yaml workspace config),
ws_specific items would not get workspace-suffixed filenames during
pull. Now falls back to workspace.name/workspaceId.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use workspace ID instead of CLI name for ws_specific file naming
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: pass workspace ID fallback to elementsToMap for ws_specific push
Without this, workspace-specific files (e.g., a.admins.resource.yaml)
were not recognized during push when no wmill.yaml or git branch was
available, causing spurious deletions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ui nits
* nit
* Fix variable edit when only editing ws_specific
* mark_linked_variables_ws_specific
* Helper label
* Support json format alongside yaml
* Fix file naming push/pull asymetry & ws_specific orphans
* Revert all CLI diffs
* CLI now appends the remote ws_specific list to the local specificItems
* UI for Env switcher
* Refactor Resource/Variable editors to use dumb component
* Refactor side effects
* Editor works with multi workspaces
* Fix can_save
* Fix As JSON
* nit
* UI nits
* list_ws_specific_versions as pl sql function to avoid round trips
* UI Nits
* Per-workspace version read-only check
* fix: reset session context in list_ws_specific_versions to prevent RLS leakage
The function calls set_session_context() in a loop. Although SET LOCAL is
transaction-scoped (so settings revert at autocommit), defending against
the function being invoked inside a longer outer transaction:
- wrap the loop in a sub-block with EXCEPTION WHEN OTHERS that resets
the session to a deny-default (windmill_user, empty session.* GUCs)
before re-raising,
- on the happy path, reset to the same deny-default at the end of the
function.
* feat: audit auto-marked ws_specific variables
When a resource is saved as ws_specific, every variable referenced via
$var: inside its value is auto-INSERTed into ws_specific. Previously
this happened silently. Now:
- mark_linked_variables_ws_specific takes the authed user,
- the INSERT uses RETURNING path so we know exactly which variables
were freshly flipped (not the ones already ws_specific),
- each newly flipped variable gets a 'variables.set_ws_specific' audit
entry pointing at the resource that triggered it.
* perf: skip mark_linked_variables_ws_specific when nothing relevant changed
update_resource was calling mark_linked_variables_ws_specific on every
save when the resource was ws_specific, even on a description-only or
label-only edit. Gate the call on `ns.value.is_some() || ns.ws_specific
== Some(true)` so we only re-mark when the $var: refs could actually
have changed or ws_specific was freshly enabled.
* docs: explain asymmetric ws_specific toggle in resource tooltip
Enabling the resource's 'Workspace specific' toggle silently marks
every variable referenced via $var: inside the value as ws_specific,
but disabling it does not un-mark those variables (they may be
referenced by other resources). Surface this in the tooltip so users
know what to expect.
* fix: surface non-404 errors when fetching ws_specific items in CLI sync
mergeWsSpecificFromServer was catching every error from listWsSpecific
and logging it at debug. That's correct for old servers without the
endpoint (404), but a 401/403/network failure would silently produce an
incomplete sync. Now distinguish 404 (debug, expected) from everything
else (warn with status + message) so users notice when the merge fails
for real reasons.
* perf: collapse compare_two_variables presence checks into one round-trip
The early-return path was issuing four sequential EXISTS queries
(ws_specific × {source, fork}, variable × {source, fork}). Combine
them into a single SELECT so the per-variable diff cost drops ~4x.
* sqlx prepare
* docs: clarify has_sql_updates invariant in update_variable
The else branch of the npath resolution is only reachable for non-rename
edits (labels-only, ws_specific-only) because ns.path being Some always
forces has_sql_updates=true at the top of the function. Add a debug_assert
and a comment explaining the invariant so a future change that decouples
ns.path from has_sql_updates trips immediately. Also use `path` directly
instead of unwrap_or_default-ing ns.path, since we know it's None here.
* chore: drop redundant ws_specific type augmentations
ListableResource and ListableVariable from $lib/gen now include
`ws_specific?: boolean` after the openapi.yaml additions in this
branch were regenerated. The intersection types in resources/+page
and variables/+page were duplicating the field — drop them.
* Put WsSpecificVersions toggle in top drawer bar
* nit size
* feat: detect local-only ws_specific items on sync push
When wmill.yaml lists a resource/variable in specificItems but the
remote isn't yet marked ws_specific for that item, sync push silently
dropped the flag because:
1. file-content diff alone never noticed (ws_specific is metadata, not
YAML body) — push{Resource,Variable} were never called for those
items;
2. even when called, isSuperset(local, remote) returned true and the
early-return skipped the API call.
Now:
- mergeWsSpecificFromServer returns the raw server list alongside the
merged config so push can compare 'in local' vs 'in server';
- a new computeWsSpecificFlagOnlyPushes helper walks the local file map,
finds ws_specific-flagged paths absent from the server list, and the
push function injects them as synthetic 'edited' changes (same before
and after content) so the standard display + apply pipeline picks
them up;
- push{Resource,Variable} no longer early-return when content matches
but the ws_specific flag differs.
Pull is unaffected — only the push-side caller of mergeWsSpecific takes
the new (merged, serverItems) tuple.
* getDeployTo for selected ws
* refactor: ws_specific kind handling, support .json files
The ws_specific helpers had two warts:
1. computeWsSpecificFlagOnlyPushes hardcoded `.resource.{yaml,json}` /
`.variable.{yaml,json}` magic strings, even though the existing
getTypeStrFromPath / removeType helpers already do that work and
already cover both extensions.
2. isSpecificItem / isItemTypeConfigured only matched `.yaml` paths,
so users with opts.json local files got no specificItems coverage
at all — patterns from wmill.yaml (and from mergeWsSpecificFromServer)
are expressed with `.yaml`, and a `.json` file never matched.
Changes:
- Replace WS_SPECIFIC_KIND_MAP (a closed enum of resource+variable)
with configKeyForItemKind, a generic kind→SpecificItemsConfig key
mapping. Triggers fold into 'triggers' via the `_trigger` suffix,
so adding a kind to the backend's list_ws_specific_versions doesn't
require a CLI change.
- mergeWsSpecificFromServer now appends `${item.path}.${item.item_kind}.yaml`
through the same helper.
- computeWsSpecificFlagOnlyPushes uses getTypeStrFromPath + removeType,
gated by configKeyForItemKind. No more magic strings.
- isSpecificItem and isItemTypeConfigured normalize trailing `.json` to
`.yaml` once at the entry, so a single set of patterns covers both
extensions for the same logical item.
* refactor: dedicated change type for ws_specific flag-only pushes
Previously the sync push code injected a synthetic 'edited' Change with
before === after to nudge the apply loop into calling pushResource /
pushVariable for ws_specific-flag-only diffs, and a guard inside those
two functions skipped the early-return when the flag differed. The
contract was implicit and easy to break — any future 'skip identical
edits' optimization in the change pipeline would silently drop these
pushes.
Replace with an explicit Change variant:
type WsSpecificFlag = {
name: 'ws_specific_flag';
path: string;
kind: string;
wsSpecific: boolean;
};
The push apply loop now has a dedicated branch for it that calls
wmill.updateResource / updateVariable with just the ws_specific flag.
prettyChanges renders it on its own line. The dry-run JSON output picks
it up via the existing change.name / change.path passthrough.
The defensive wsSpecificMatches check inside push{Resource,Variable} is
no longer needed (sync push doesn't go through them for flag-only
diffs) and is reverted.
* drop folders
* feat(cli): warn on remote ws_specific items missing from local config
When 'wmill sync pull' fetches the server's ws_specific list, items the
server marks as ws_specific but that aren't matched by the local
wmill.yaml's specificItems patterns now produce a warning. The merge
already preserves correctness (those items are still treated as ws_specific
during this pull), but the user's config drifts from the remote — and a
later push from another machine without that config would push the item
as non-ws_specific. Surface the drift so the user can update wmill.yaml.
Also filter ws_specific_flag changes out before preCheckPermissionedAs
(it expects added/edited/deleted only and they have no content payload
so on_behalf_of resolution doesn't apply).
* fix(cli): scope ws_specific drift warning to items in this pull's changes
Previously the warning iterated every ws_specific item the server returned,
producing log spam for items unrelated to the current pull (items that
exist locally with no change, or items the user has nothing to do with
this round). Move the loop after compareDynFSElement and only warn for
items whose path appears in the changes list — i.e., items the user is
actually pulling right now.
* fix: clean up linked-side ws_specific rows on resource/variable delete
Three places left orphaned ws_specific rows behind:
1. delete_resource deleted the resource's own ws_specific row and the
linked variables, but never the ws_specific 'variable' rows that
mark_linked_variables_ws_specific had auto-inserted for those
variable paths.
2. delete_variable deleted its own ws_specific row and the linked
resource at the same path, but never a ws_specific 'resource' row at
that path.
3. delete_resources_bulk didn't even cascade to linked variables, let
alone clean up their ws_specific rows.
A new resource or variable later created at one of those paths would
silently inherit a stale ws_specific flag — list_ws_specific would
report it as workspace-specific, workspace diffs would treat it as
'no changes', and CLI sync would skip it.
Fix:
- delete_resource: DELETE FROM ws_specific WHERE item_kind = 'variable'
AND path = ANY(linked_var_paths) before the linked-variable delete.
- delete_variable: DELETE FROM ws_specific WHERE item_kind = 'resource'
AND path = path before the linked-resource delete.
- delete_resources_bulk: collect $var: refs from each bulk-deleted
resource (mirror of single delete), then delete ws_specific 'variable'
rows AND the variable rows themselves. Brings bulk delete in line with
single delete semantics, including the orphan cleanup.
* fix: gate list_ws_specific by resource/variable RLS
The endpoint queried ws_specific directly under user_db, but ws_specific
itself has no per-item RLS — only a workspace-level column. Any workspace
member could enumerate every ws_specific path including those in folders
they lack read access to (e.g. f/finance/prod_db_creds), revealing path
existence that list_resources / list_variables would have hidden.
Add EXISTS clauses against resource and variable so the same path-based
RLS policies that govern those tables (see_own / see_member /
see_extra_perms_user / see_extra_perms_groups / see_folder_extra_perms_user)
also gate visibility here. The user transaction already establishes the
session context; the joins make the policies apply.
* only resources and variables
* fix(cli): make workspace-specific path mapping handle .json files
isSpecificItem() was extended to normalize .json -> .yaml so .json
files could be matched against patterns, but the surrounding helpers
remained yaml-only:
- toWorkspaceSpecificPath only mapped folder.meta.yaml / settings.yaml
/ .X.yaml — a foo.resource.json went through unchanged, so the
workspace-specific filename was never produced.
- fromWorkspaceSpecificPath only matched .yaml extensions — pushing
foo.dev.resource.json could not map back to foo.resource.json.
- isCurrentWorkspaceFile / isWorkspaceSpecificFile regexes ended in
\.yaml$, missing every branch-specific .json file.
Replace the literal '.yaml' anchors with '(yaml|json)' alternations,
preserve the actual extension on round-trips, and rename the helper
buildYamlTypePattern -> buildItemTypePattern (it never had anything
extension-specific in it). getFileTypeSuffix now returns the matching
suffix for either extension. Changed:
- getFileTypeSuffix
- toWorkspaceSpecificPath / fromWorkspaceSpecificPath
- isCurrentWorkspaceFile / isWorkspaceSpecificFile
- isTriggerFile / isScheduleFile
isItemTypeConfigured / isSpecificItem don't need touching — their
checks run after normalizeJsonToYaml(), which already collapses both
extensions to .yaml at the entry.
* fix: create_resource?update_if_exists=true honors ws_specific=false
The upsert path matched on `unwrap_or(false)`, so an explicit
`ws_specific: false` and an absent flag were indistinguishable — both
fell through with no DELETE on the existing ws_specific row. Callers
trying to clear the flag via PUT-with-update_if_exists silently saw
their request ignored.
Mirror update_resource's three-way handling:
Some(true) -> INSERT (+ mark linked variables)
Some(false) -> DELETE (only when update_if_exists, since a pure
create has no existing row anyway)
None -> leave the existing flag alone
create_variable doesn't have an upsert path (no ON CONFLICT), so the
same bug doesn't apply there.
* sqlx prepare
* test: cover ws_specific cleanup, RLS filtering, upsert clearing, and CLI .json paths
Backend (backend/tests/ws_specific.rs + fixture):
- test_linked_delete_cleanup: creates a ws_specific resource that
references a variable via $var:, deletes the resource, asserts the
cross-kind ws_specific row for the auto-marked variable is also
removed. Then does the inverse for delete_variable, verifying the
ws_specific 'resource' row at the same path is cleaned by variable
delete.
- test_list_ws_specific_filters_by_rls: admin creates ws_specific items
in u/test-user/ and u/test-user-2/; verifies admin sees both via
list_ws_specific while a non-admin (test-user-2) only sees their own
path — the RLS see_own policy on the joined resource/variable tables
hides the other.
- test_create_resource_upsert_clears_ws_specific: covers the three-way
Option<bool> handling on the upsert path: Some(true) inserts,
Some(false) clears the existing row, None leaves it alone.
CLI:
- specific_items_unit.test.ts: add 14 tests covering toWorkspaceSpecificPath
/ fromWorkspaceSpecificPath / isWorkspaceSpecificFile /
isCurrentWorkspaceFile / isSpecificItem / isItemTypeConfigured for
.json files (variable, resource, trigger, schedule, folder.meta,
settings).
- ws_specific_flag_only_unit.test.ts (new): covers
computeWsSpecificFlagOnlyPushes — emits flag-only changes only for
resource and variable kinds (the backend's list_ws_specific_versions
scope), does not emit for schedules or triggers, returns empty when
serverItems is null (older server), respects existing server entries,
preserves .json extension on filePath.
- Export computeWsSpecificFlagOnlyPushes so it can be unit-tested.
* perf: index workspace_settings.deploy_to for the recursive CTE
list_ws_specific_versions's recursive CTE probes WHERE ws.deploy_to =
r.ws_id every iteration; without an index on workspace_settings.deploy_to
each iteration seq-scans the table — at 10M workspaces with the depth
cap of 32 that's up to 320M row reads per call.
deploy_to is sparse (most workspaces don't deploy anywhere), so a
partial index WHERE deploy_to IS NOT NULL stays small while still
covering every probe. Tucked into the existing migration since the
function and the index ship together.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(forks): handle triggers and schedules in wmill workspace merge
Closes#9001. Brings CLI parity with the merge UI by routing trigger and
schedule diffs through the existing workspace_diff tally infrastructure
and lifting the deploy logic into the shared windmill-utils-internal
module.
- Backend: extend tally + compare to all 10 trigger kinds + schedule;
new compare_two_trigger_or_schedule helper using to_jsonb minus runtime
ignore set; CompareSummary gains schedules_changed/triggers_changed.
- Operational-state invariant: fork operations never flip target's
mode/enabled. Triggers strip mode/enabled in both UI and CLI deploy
payloads (preserved by is_mode_unspecified on backend). Schedules drop
the setScheduleEnabled mirror entirely on merge — EditSchedule lacks
enabled by design.
- Shared module: DeployKind extended with schedule + per-kind triggers;
DeployProvider gains per-kind dispatch methods.
- Frontend: ~600 lines of client-side trigger-diff machinery deleted;
rows flow through comparison.diffs like every other kind. Diff drawer
returns full GET response stripped of runtime fields, matching backend
semantics. Default selection excludes triggers/schedules (opt-in).
- CLI (merge.ts): per-kind provider, GCP-specific transforms (audience
reset, base_endpoint with /api stripped to match frontend), summary
table rows for Schedules/Triggers, default-deselect mirroring the UI.
- Bumps windmill-utils-internal to 1.5.0 (new exports for trigger
per-kind dispatch); frontend depends on ^1.5.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(enterprise): clarify [ee] prefix applies whenever an EE companion PR exists
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 6ee680c25e3413d928fc22002be6deb118092668
This commit updates the EE repository reference after PR #557 was merged in windmill-ee-private.
Previous ee-repo-ref: ad35a056627656fd426fb19856ea945955d4727f
New ee-repo-ref: 6ee680c25e3413d928fc22002be6deb118092668
Automated by sync-ee-ref workflow.
* fix(forks): preserve target state on merge update, mirror source on create
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(forks): strip server-managed trigger fields and honor --include with --skip-conflicts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* fix(cli): resolve cross-folder relative imports during lockgen on fresh DB
On a fresh workspace, lockfile generation for scripts that imported other
scripts via cross-folder relative imports (or barrel re-exporters) failed with
"Failed to find relative import" because the dep job's bun build hit the
server before any helper was deployed. Three independent bugs combined to
produce this:
1. wmill sync push --auto-metadata regenerated locks per script without
building a DoubleLinkedDependencyTree or calling uploadScripts, so
temp_script_refs was never sent to dependencies_async.
2. wmill script generate-metadata (the deprecated alias) had its own old
in-line implementation that bypassed the tree entirely.
3. The TypeScript WASM parser dropped re-exports (export * from, export { x }
from) when called with skip_type_only=false — the path used by
parse_relative_imports — so barrel files looked like leaves to the CLI's
dependency tree and their sibling helpers were missing from
temp_script_refs.
Fix:
- sync.ts: --auto-metadata mirrors generate-metadata's flow (dryRun pass to
populate tree → propagateStaleness → uploadScripts → real pass with tree).
- script.ts: deprecated wmill script generate-metadata now delegates to the
canonical generateMetadata, which already does the tree+upload dance.
- parser-ts: visit_export_all and visit_named_export had inverted skip_type_only
guards; aligned with visit_import_decl's pattern.
Includes 4 E2E tests reproducing each customer-hit failure path and a Rust
unit test for the re-export parser fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: bump windmill-parser-wasm-ts to 1.695.0
Pin the parser package to the version published with the re-export fix
(visit_export_all / visit_named_export skip_type_only=false) so the CLI
and frontend pick it up at the next release.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): restore legacy stale-check in deprecated alias, add tree to gen pass
Delegating wmill script generate-metadata fully to the canonical handler
broke 4 workspace_deps_filter tests that rely on the legacy hash-with-deps
formula and the "No metadata to update" output string.
Restore the original in-line implementation (legacy stale-check preserved),
but add a DoubleLinkedDependencyTree + uploadScripts pass before the actual
generation step. The customer's bug only manifests on real lockgen, not on
the dry-run staleness check, so this preserves the existing test contract
while still fixing cross-folder relative imports for the deprecated alias.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The config module imports node:fs/promises (stat, mkdir), which breaks
non-Node bundlers like the Cloudflare Workers build of the hub. The
windmill SPA frontend got away with it via tree-shaking, but stricter
runtimes choke on the bare node: import even when unused.
Stop re-exporting ./config from the main entry and expose it via a
windmill-utils-internal/config subpath instead. CLI code already
deep-imports the source file, so it is unaffected. Bumps the package
to 1.4.0 and updates the frontend dependency to match.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): add --as-superadmin flag to workspace list-remote
Wires the existing /workspaces/list_as_superadmin endpoint into the
CLI so superadmins can enumerate every workspace on an instance from
CI tooling, not just the ones the calling user is a member of.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: regenerate CLI prompts and document the regen step
Adds an AGENTS.md note pointing to system_prompts/generate.py so future
CLI command edits keep the agent-facing CLI docs in sync, and
regenerates the auto-generated outputs for the new --as-superadmin flag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: surface scope errors as 403 and show real message in CLI
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: address review feedback on scope error PR
- Backend: also patch handler-level check_scopes (lib.rs:223) — without
this, endpoints using check_scopes (scripts, flows, jobs, …) still
returned 401 for scope failures, which the CLI would render as the
misleading auth message.
- CLI: strip backend file refs and the duplicated "Permission denied:" /
"Not authorized:" prefix from the surfaced error body.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat(forks): strip operational state from triggers/schedules on git-sync export
When the source workspace is a fork (`wm-fork-*`), the tarball export now
omits `mode` from triggers and `enabled` from schedules. The trigger update
handler also preserves the existing DB `mode` when both fields are absent
from the request, instead of falling back to the BaseTriggerData default.
This prevents a fork's git-sync round-trip from flipping the parent
workspace's enabled/disabled state when a merge applies the fork's YAML
back to main.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(forks): opt-in fork_triggers flag clones triggers/schedules disabled
Adds `workspace.fork_triggers` (default false) and a matching field on
CreateWorkspaceFork. When the user opts in, fork creation also runs
clone_triggers_and_schedules: every row in schedule and the ten
*_trigger tables is copied to the fork with mode='disabled' /
enabled=false. Listener identifiers (group_id, replication_slot_name,
subscription_name, …) are copied verbatim — the runtime suffix that
prevents the fork from competing with the parent ships in a follow-up
PR.
native_trigger is intentionally skipped: those triggers manage external
webhook state we don't want duplicated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(forks): warn before enabling triggers/schedules that conflict with parent
set_trigger_mode and schedule's set_enabled now check whether the parent
workspace has the same path actively enabled. If so, the call is rejected
with a `fork-conflict:<kind>:<parent_id>` error unless the request includes
`force=true`. The frontend interprets the prefix to surface a confirm-to-
proceed dialog.
This is the placeholder safety net until the Phase 3 listener-suffix work
removes the conflict for the namespaceable kinds (Kafka/MQTT/NATS/Postgres/
Azure/GCP-CreateNew). For SQS, GCP-Existing, and schedules — where there's
no namespacing fix — the warning is the durable solution.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(forks): UI: opt-in clone-triggers checkbox + confirm-on-fork-conflict
Adds the user-facing surface for the fork-trigger work:
- CreateWorkspaceInner: new "Clone triggers and schedules" toggle in the
fork-creation dialog (default off). Sends fork_triggers in the request.
- forkConflict utility: detects the `fork-conflict:<kind>:<parent_id>`
error string from the backend, shows a confirm() dialog explaining
why the action is blocked, retries with `force: true` if accepted.
- Wires withForkConflictRetry into every trigger setMode and the
schedule setEnabled call, both in the per-kind editor components and
the +page.svelte list views (HTTP, websocket, kafka, NATS, SQS, MQTT,
GCP, Azure, Postgres, email, schedule).
OpenAPI spec gains the `force` field on each setmode/setenabled body.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(forks): CLI --fork-triggers flag, fork-trigger docs, skill update
- Adds --fork-triggers boolean to wmill workspace fork; passes
fork_triggers through to the create_fork API call.
- New docs/fork-triggers.md describing the model end-to-end (default,
opt-in clone, merge-direction filter, conflict warning, future
runtime-suffix work).
- Updates the adding-a-trigger SKILL.md to mention the fork-export
ignore-keys participation and the clone_triggers_and_schedules
block that new trigger kinds must extend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: regenerate sqlx offline query cache for fork-trigger SQL
* fix(forks): replace browser confirm() with ConfirmationModal for fork conflict
The fork-conflict warning previously used the browser's native confirm()
which doesn't match Windmill's design system. Switches to a singleton
ConfirmationModal mounted at the (logged) layout root, driven by a new
forkConflictModal store. The withForkConflictRetry helper now sets the
store and awaits the user's choice via a Promise, instead of blocking
on window.confirm.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(forks): filter unchanged triggers in merge UI, add diff view, surface parent-only ones
The fork merge UI listed every trigger from the fork as a deployable item
regardless of whether it differed from the parent — so a fork created with
fork_triggers=true (which clones triggers in disabled state, otherwise
identical) showed every trigger as a "Fork-only" change. The 'Update
current' tab also missed triggers newly created in the parent that the
fork hadn't pulled yet.
This refactor:
- fetchAllTriggers now lists both fork and parent in parallel for each
trigger kind, then merges by path.
- Computes a per-trigger `changeKind` (new / modified / deleted-in-source)
using a JSON comparison that strips runtime + fork-local fields
(mode/enabled/server_id/last_server_ping/edited_at/edited_by/etc.) so
the disabled-on-clone difference doesn't show up as a change.
- Filters the trigger items in deployableItems by the current direction:
Deploy mode shows fork-side new/modified, Update mode shows parent-side
new/modified.
- Replaces the always-on "Fork-only" badge with proper New/Modified
badges and surfaces a Diff button (modal Drawer + Monaco DiffEditor)
for modified triggers — the diff strips the same ignored fields so
users see only the meaningful config differences.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(forks): always clone triggers/schedules disabled, drop opt-in flag
Disabled triggers and schedules are inert — no listener attaches, no cron
fires — so cloning them by default is safe by construction. Drops the
fork_triggers opt-in flag introduced earlier in this PR:
- Drops workspace.fork_triggers column (migration removed)
- Removes fork_triggers from CreateWorkspaceFork (API + OpenAPI)
- Removes the conditional in create_workspace_fork — clone always runs
- Removes the toggle from the fork-creation dialog
- Removes --fork-triggers from `wmill workspace fork`
- Updates docs/fork-triggers.md and adding-a-trigger SKILL.md
The merge UI continues to exclude triggers from the deploy/update default
selection, so a routine merge from a fork doesn't accidentally push
trigger config the user hasn't intentionally changed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(http-triggers): scope route exists check by workspace, skip non-workspaced clones in forks
The non-CLOUD branch of `route_path_key_exists` self-excluded by trigger
path alone, which silently masked cross-workspace collisions once forks
started cloning trigger rows verbatim. Tighten it to exclude only the
exact `(workspace_id, path)` row.
Fork creation also now skips non-workspaced HTTP triggers — their URL
has no workspace prefix, so a clone collides with the parent at the
matchit router (which silently drops one of two duplicates) and there is
no namespacing escape hatch. The clone copies all rows when CLOUD_HOSTED
or HTTP_ROUTE_WORKSPACED_ROUTE forces every route workspaced regardless.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(forks-ui): silent cancel on enable conflict, clean up trigger rows in compare view
forkConflict helper now returns undefined when the user dismisses the
modal instead of throwing, so the redundant 'Cannot enable: undefined'
toast no longer appears.
CompareWorkspaces trigger rows now mirror the script row layout: drop
the redundant Disabled badge and the Trash/Details buttons (both belong
on the dedicated trigger pages, not in the deploy/compare view); pass
triggerKind through so RowIcon picks the right kind-specific icon; move
extraLabel into the summary line; replace the yellow Modified badge
with the same green ↗ ahead / blue ↘ behind treatment scripts use.
Trigger diff drawer: switch JSON → YAML for parity with DiffDrawer, fix
zero-height monaco render with className=!h-full, drop the redundant
Original/Modified label banner above the diff.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(email-trigger): scope local_part exists check, skip non-workspaced clones in forks
Mirrors the HTTP route fix for the email-trigger non-CLOUD `email_exists`
check (in EE) which had the same path-only self-exclusion bug, and the
fork clone of `email_trigger` rows which copied non-workspaced
`local_part` verbatim. Skip non-workspaced rows in the clone unless the
instance is CLOUD_HOSTED (where lookup is workspace-scoped natively).
EE companion change in windmill-trigger-email/src/handler_ee.rs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 78512dd73b4a1c9f70574cff863374179e3a621b
This commit updates the EE repository reference after PR #554 was merged in windmill-ee-private.
Previous ee-repo-ref: 1ac77f50747b58e720a11162dfd309bc252a24ab
New ee-repo-ref: 78512dd73b4a1c9f70574cff863374179e3a621b
Automated by sync-ee-ref workflow.
* fix(forks): always-warn on parent row, kind-specific modal copy, cancel-aware toggles
- Conflict check now fires whenever the parent has the path (regardless of
parent's mode), since the cloned upstream identifier is shared by
construction; closes the Postgres slot-takeover gap when the parent is
disabled. Schedule's set_schedule_enabled gets the same treatment.
- Skip the warning entirely for HTTP and Email via a new
TriggerCrud::FORK_CONFLICT_ON_ENABLE const — both kinds are workspace-
scoped at runtime so cloned rows can't collide with the parent.
- Modal copy branches by failure family: split-events (Kafka/NATS/MQTT/SQS/
GCP/Azure), duplicate-firing (Websocket/Schedule), slot-takeover
(Postgres). Generic fallback for unknown kinds.
- withForkConflictRetry now returns boolean (true=committed, false=
cancelled). TriggerModeToggle reuses its existing innerTriggerMode local
state via a function binding for the regular Toggle, snapping back to
the prop when onToggleMode signals a cancel — needed because the native
bind:checked diverges from the parent's prop after a click and Svelte's
reactivity won't re-push a same-valued prop down. Schedule list page
uses {#key} on a reset version since it renders Toggle directly.
- Editor inners revert mode = previousMode on cancel; list pages skip the
re-fetch (loadTriggers/loadSchedules) on cancel to avoid pointless
network traffic and the schedule "Job stats loading..." flash.
- Drop withForkConflictRetry from HTTP and Email editors + list pages
since the backend never emits the conflict for those kinds.
* fix(forks-ui): widen onToggleMode types, scope schedule toggle reset by path
- TriggerEditorToolbar and TriggerSuspendedJobsModal forwarded
onToggleMode as `(mode) => void`, dropping the new boolean return so
any caller wired through them would silently no-op the cancel-revert.
Match the wider TriggerModeToggle signature.
- Schedule list page used a single resetVersion counter for every row's
{#key}, so cancelling on any one schedule remounted every <Toggle> on
the page. Switch to a per-path Record<string, number> bumped only for
the affected row.
* chore: bump ee-repo-ref to c3a4553 (email FORK_CONFLICT_ON_ENABLE override)
* fix(forks): include Suspended in conflict gate, use parent_workspace_id for fork detection
Three fixes from the Claude review on PR #8976:
- Suspended mode still attaches the listener (it just pauses auto-run of
queued jobs); two suspended fork+parent listeners would still split
Kafka events / share a PG slot. Gate set_trigger_mode on
`mode != Disabled` instead of `mode == Enabled` so Suspended also
surfaces the warning.
- workspaces_export.rs::fork_*_ignore_keys keyed off the wm-fork-* prefix
while set_trigger_mode and set_schedule_enabled key off
parent_workspace_id. Switch the export filter to query
parent_workspace_id once at the top of tarball_workspace and pass
is_fork through. The column is the contract; the prefix is a
creation-time naming convention that could in principle drift.
- TriggerModeToggle's suspend-dropdown action reassigned the non-bindable
`triggerMode` prop instead of the local `innerTriggerMode` mirror,
leaking inconsistent state if the dispatch was cancelled. Now writes
to innerTriggerMode like the Toggle's on:change handler does.
* fix(cli): skip setScheduleEnabled when local YAML lacks `enabled`
Tarball export from a fork strips `enabled` from schedules so the
fork→parent git-sync round-trip can't flip the parent's operational
state. The CLI's pushSchedule called setScheduleEnabled whenever
`localSchedule.enabled != schedule.enabled`, which evaluates truthy
when local is undefined (fork-pulled YAML) and remote is true/false —
sending `{ enabled: undefined }` that serializes to `{}` and gets
rejected by the backend (`SetEnabled.enabled` is required).
Skip the call when `localSchedule.enabled === undefined` so a sync push
of fork-pulled YAMLs preserves the target's existing enabled state
instead of erroring out. Trigger updates were already safe — the
backend's update_trigger preserves `mode` when the request omits it.
* Revert "fix(cli): skip setScheduleEnabled when local YAML lacks `enabled`"
This reverts commit 23ba7e72fc.
* feat(cli): --force flag and friendlier error on fork-conflict for schedule enable
`wmill schedule enable foo/bar` against a fork whose parent has the same
path used to surface the raw `fork-conflict:schedule:<parent>` error
body. The CLI now:
- accepts `--force` to bypass the warning (mirrors the API field and the
UI's "Enable anyway" confirmation),
- detects the `fork-conflict:` prefix on errors and prints a one-screen
explanation pointing at --force instead of the raw body.
Disable doesn't trigger the warning (the gate fires only on transitions
to listener-attaching modes), so no flag there. Trigger enable/disable
isn't exposed as a standalone CLI command — sync push goes through
updateTrigger which has its own backend mode-preservation, so no
fork-conflict surfaces from the CLI for those.
* chore: regenerate cli-commands docs after adding --force to schedule enable
* chore: update ee-repo-ref to 967f961f0a88b027d894aebd03977181129477a8
This commit updates the EE repository reference after PR #555 was merged in windmill-ee-private.
Previous ee-repo-ref: c3a4553296473932e15392a06415dd7fb9aa6591
New ee-repo-ref: 967f961f0a88b027d894aebd03977181129477a8
Automated by sync-ee-ref workflow.
* fix(forks): address CI dead-code, claude/cubic review feedback
- backend: cfg-gate `fork_trigger_ignore_keys` to match its already-gated
callsite. CI compiles with `-D warnings`, so the unused-fn under feature
combos that disable all trigger crates was breaking check_oss/check_ee/
cargo_test/test-linux/test-windows.
- cli: re-apply the `pushSchedule` undefined-skip (originally 23ba7e7,
reverted in 4d172a1). Tarball export from forks strips `enabled`, so
fork-pulled YAMLs that get sync-pushed back via `wmill schedule push`
would otherwise serialize `{ enabled: undefined }` → `{}` and the
backend's required `SetEnabled.enabled` rejects the body. Skipping
preserves the target's existing flag, which is the round-trip-safe
behavior. (`wmill workspace merge` extension to triggers/schedules is
tracked in #9001 — until then sync push is the only CLI path.)
- TriggerModeToggle suspend-dropdown action awaits onToggleMode and
resets `innerTriggerMode = triggerMode` on cancel, matching the Toggle
on:change handler. Without this, dismissing the fork-conflict modal on
a Suspend transition leaves the toggle stuck in 'suspended'.
- forkConflict: when a new modal opens with a previous resolver still
pending, resolve the older promise to false. Avoids a dangling promise
if the user clicks toggles on two rows in quick succession.
- schedules list: bump `toggleResetVersions[path]` on the
permission-denied branch so the Toggle re-mounts back to the prop's
`enabled` value. Without this, a user without write permission could
click the toggle and have it stick visually flipped.
- docs/fork-triggers.md: switch the merge-direction filter description
from `wm-fork-*` prefix to `parent_workspace_id IS NOT NULL` (matches
the code after 4dd38fe). Drop the misleading "merge-direction filter
strips identifier columns too" line in Future Work — the runtime
suffix is applied at listener attach, the stored column never carries
it, so no export filtering is needed there.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* docs(skill): document email triggers and S3 attachments
Add an "Email triggers" section to the triggers skill covering the
local-part config, the parsed_email/raw_email/email_extra_args payload,
the URL-style extras convention, where to find trigger_path (only with
a preprocessor, at event.trigger_path), and — most importantly — that
binary attachments are uploaded to the workspace S3 bucket and surface
as `{ s3: "windmill_emails/<job_id>/attachments/<filename>" }`. Scripts
must use wmill.loadS3File / wmill.load_s3_file to read them.
Also pulls EmailTrigger into the schema mappings so a real
`email_trigger.schema.yaml` is generated, and adds Email/Azure to the
trigger kinds list in the CLI agent guidance.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref for email trigger path fix
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 26184ab7a4aadfc529dcedf038aa08d36c7ad381
This commit updates the EE repository reference after PR #553 was merged in windmill-ee-private.
Previous ee-repo-ref: 318a46897a605dc9be3817901f35ba5a99a0a525
New ee-repo-ref: 26184ab7a4aadfc529dcedf038aa08d36c7ad381
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(cli): canonical lockfile hashes + lock upgrade migration to v3
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): use __app_hash subpath in rehash missing-entry check
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): run sync pull lockfile auto-fill regardless of changes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: regenerate system prompts for new lock and rehash-only commands
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): address review feedback on lock upgrade
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): drop v3 marker; always run fallback; fail-fast on unknown lockfile version
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): drop yaml-round-trip legacy hash variant; recover via --rehash-only
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): include legacy hash in script push staleness warning check
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* revert(cli): drop canonical hash formula; keep raw-bytes hashing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* perf(cli): reuse change-tracker map for sync pull lockfile auto-fill
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): address review feedback on rehash-only
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test(cli): pin lockfile hash + yaml format and cover regression cases
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test(cli): byte-stable snapshot tests for flow.yaml format
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test(cli): add app and script-metadata yaml snapshot fixtures
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): address claude review on rehash-only
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(cli): factorize script-path to remote-path derivation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): address claude + cubic review (dry-run mutation, rehash short-circuit)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(cli): make rehash a subcommand and factorize fs walks
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): normalize line endings in yaml snapshot tests for windows ci
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): address review feedback on rehash + auto-fill
- Flat-layout scripts now clearGlobalLock before rehash write so legacy
./-prefixed duplicates get cleaned up (matches flow/app behavior).
- Add MalformedLockfileError; sync pull auto-fill re-throws it alongside
UnknownLockVersionError instead of silently warning + continuing.
- Document the legacy step-removal false-negative in
isFlowDirectlyStale / isAppDirectlyStale and the categorizeLocalFiles
ignore-filter invariant.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): only preserve case for raw-app runnableIds, not app/flow summaries
PR #8940 stopped lowercasing in sanitizeForFilesystem to fix#8939, where
a raw-app runnableId like CamelCaseTSRunnable produced a CamelCase YAML
metadata file but a lowercased code file, making them desync and
register as duplicate runnables on push.
That fix overshot. sanitizeForFilesystem is also reached by
newPathAssigner, which serves normal apps and flows where the input is
the script's human summary ("Get Users Data") rather than an identifier.
There the on-disk filename is the only artifact — there's no companion
YAML to keep in sync — so lowercasing was the right behavior. Removing
it changed both the on-disk filename and the !inline reference in
app.yaml / flow.yaml from get_users_data.inline_script.ts to
Get_Users_Data.inline_script.ts on the next pull, surfacing as
unwanted case churn for users updating to 1.693.x.
Add a preserveCase option to sanitizeForFilesystem (default false →
lowercase). newRawAppPathAssigner opts in; newPathAssigner stays on
the default. Update unit tests accordingly and add an end-to-end
raw-app round-trip in raw_app_sync.test.ts that pushes a CamelCase
backend runnable, pulls it back, and asserts both YAML and code file
preserve case with no lowercase orphan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(cli): use readdir for exact-case orphan check on Windows
The CamelCase round-trip test used fileExists("camelcasetsrunnable.ts")
to assert no lowercase orphan was produced, which false-positives on
Windows since the filesystem is case-insensitive and resolves the
lookup to the existing CamelCaseTSRunnable.ts. Switch to readdir +
toContain so the exact on-disk casing is compared identically on Linux
and Windows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously build-npm.ts emitted "*" for every wasm parser dep. Combined
with lockfiles pinning the first resolved version, users who installed
when a parser was briefly behind got stuck on a stale wasm even after
the registry caught up — surfacing as "TypeError: parse_ts_relative_imports
is not a function" warnings during sync/generate-metadata.
Forward the dev package.json specs into the generated package.json
instead, and pin all 12 parsers to the exact versions published on npm
(mirroring frontend/package.json).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>