Introduces UserDraft, a key-value store keyed by
`{workspace}/{itemKind}/{path}` and backed by localStorage. Supports
save/get/remove plus a reactive use() handle so multiple component
instances observing the same draft stay in sync via a shared $state
loaded through useLocalStorageValue. Designed to host drafts for
scripts, flows, apps, raw apps, resources, variables, and all trigger
kinds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Strip the per-editor localStorage autosave for flows, apps and raw apps,
along with the associated restore toasts and diff actions, so we can
replace them with a unified UserDraft service in a follow-up. The
backend DraftService (DB-backed drafts) is untouched.
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>
* [ee] feat(secret-backend): add Workload Identity Federation for Azure Key Vault
Make `client_secret` optional. When omitted, Windmill falls back to
Azure Workload Identity Federation: it reads the projected
service-account JWT from AZURE_FEDERATED_TOKEN_FILE and exchanges it
with Entra ID via `client_assertion`, no long-lived secret stored on
the instance. Same code path covers AKS (workload-identity admission
webhook auto-injects the env vars) and any other Kubernetes cluster
federated to Entra ID (EKS/GKE/self-hosted).
- backend: relax client_secret to Option (already was), update doc
comment + OpenAPI description; the actual auth-branching logic lives
in the EE companion file (azure_kv_ee.rs).
- frontend: drop client_secret/token from canSubmit so saving with an
empty secret is allowed; add inline help under the Client Secret
field pointing to AZURE_FEDERATED_TOKEN_FILE; mark the field optional.
- ee-repo-ref: bump to the EE companion commit.
EE companion: see windmill-ee-private branch azure-keyvault-managed-identity.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] chore: bump ee-repo-ref for blank-client_secret fix
Picks up the EE-side fix (windmill-ee-private c7c0a23) that treats blank
`client_secret` as workload-identity instead of POSTing an empty string
to Entra ID. Addresses Codex review on PR #9061.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to c8d100d74b8de6bd26fc973d5edbd8853d54dd8b
This commit updates the EE repository reference after PR #561 was merged in windmill-ee-private.
Previous ee-repo-ref: c7c0a23459b0e7416a045a279346cc48b30eed32
New ee-repo-ref: c8d100d74b8de6bd26fc973d5edbd8853d54dd8b
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>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* 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>
* [ee] feat(kafka-trigger): enable librdkafka OIDC for OAUTHBEARER
Adds the curl-static feature to the rdkafka dependency so librdkafka is
built with libcurl + OpenSSL, which it requires to perform the
client_credentials token exchange used by sasl.oauthbearer.method=oidc.
Without this feature librdkafka rejects sasl.oauthbearer.token.endpoint.url
at runtime with "OAuth/OIDC depends on libcurl and OpenSSL which were not
available at build time".
Pairs with windmill-ee-private#<TBD> which adds the SASL_SSL_OAUTHBEARER
KafkaResourceSecurity variant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to c6d55247a7fd951f561e0b2ad2ac13051274aa77
This commit updates the EE repository reference after PR #559 was merged in windmill-ee-private.
Previous ee-repo-ref: ce88502234f3f5886d2094dc25b30a702de9b8b1
New ee-repo-ref: c6d55247a7fd951f561e0b2ad2ac13051274aa77
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>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* feat: allow external domain override for user-facing links
* refactor: use plain var + setter for externalDomain
* chore: expose externalDomain in package exports
* fix: handle singlestepflow zombies and stop filtering them from runs page
* fix: support singlestepflow in batch_rerun_jobs
Previous PR added singlestepflow to list_selected_job_groups so the BatchReRun
pane shows them, but batch_rerun_jobs_inner still joined on kind = 'script' /
'flow' with j.runnable_id (which is NULL for SingleStepFlow), so the rows were
silently filtered out — user sees the option, click Re-run, gets zero successes.
Mirror the norm_kind CTE projection from list_selected_job_groups inside
batch_rerun_jobs_inner: pull the wrapped runnable type and pinned script hash
from raw_flow.modules[id='a'], cast back to JOB_KIND so the existing handler
dispatch works unchanged. Path-based schema fallback so input_transforms still
resolve at rerun time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: project singlestepflow in batch-rerun schema lookups
Codex review pointed out two follow-on regressions from the previous fix:
(1) list_selected_job_groups returned schemas with script_hash=null and
schema=null for singlestepflow rows because the inner schemas subquery still
joined runnable metadata via j.runnable_id (NULL for SingleStepFlow). The
BatchReRun pane consumes every selected.schemas entry through
mergeSchemasForBatchReruns / buildExtraLibForBatchReruns, both of which
assume real schema objects.
(2) When use_latest_version=true, batch_rerun_handle_job re-fetched
latest_schema from v2_job filtering jb.kind='script' or 'flow' — neither
matched singlestepflow, so schema came back NULL and every input_transforms
entry silently no-op'd.
Both queries now project singlestepflow rows via raw_flow.modules[id='a'] —
norm_kind for dispatch and effective_hash for the schemas join, plus a
path-based latest-schema fallback so flow-wrapped SSF (no version pinning)
and any SSF whose pinned hash has been deleted still resolve.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: add batch_rerun integration tests, fix SSF hash hex parsing
Adds 11 integration tests against /jobs/run/batch_rerun_jobs and
/jobs/list_selected_job_groups (both endpoints had zero CI coverage).
Tests cover the full 4-kind × 3-mode matrix: regular Script and Flow
(baseline regression for the SQL refactor), script-wrapped and flow-
wrapped SingleStepFlow (regression for the bugs this PR fixes), and a
mixed-kind batch.
Writing the tests caught a real bug in the previous commit: ScriptHash
serializes as a 16-char hex string in raw_flow.modules[a].value.hash
(per the custom Serialize impl in windmill-types/scripts.rs), not as
an integer. The earlier `(m->'value'->>'hash')::bigint` cast worked
on the hand-inserted SQL fixture I'd used for live testing (which
embedded the hash as a raw integer) but failed in production where
all SSF jobs are pushed via JobPayload::SingleStepFlow's serialized
form. Replaced with `('x' || lpad(hex, 16, '0'))::bit(64)::bigint` —
preserves the twos-complement bit pattern so both positive and
negative i64 hashes round-trip correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update SQLx metadata
---------
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>
* Navigate with arrows
* Jumps to other side item + load 30 more
* No workspace selector
* Recommendations Claude check
* Navigation horizontal
* Same
---------
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>
* fix(flows): inherit flow_env in sub-flow predicates
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(flows): align flow_env lookup with get_root_job_id and tighten gate
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(flows): drop recursive CTE, root_job propagation suffices
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(flows): walk via flow_innermost_root_job to respect imported-flow scope
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(flows): remove flow_env API endpoint, dead code from deno_core era
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: add ai chat resource action buttons
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: avoid proxied drawer state equality
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: show tool action cards
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Workspace rows in the sidebar picker and the Edit button on the run detail
page now render as `<a href>`, so middle-click and Ctrl/Cmd-click open
them in a new tab. Plain clicks keep their existing in-tab behavior
(workspace store switch / args prefill via `$initialArgsStore`).
The Edit href carries `?workspace=<current>` so the new tab loads the
editor in the same workspace as the run.
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>
REVIEW.md is shared across Claude/Codex/Pi reviewers; CLAUDE.md is
Claude-specific noise that doesn't belong in cross-CLI review policy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Policy is not GitHub-specific (also used by local-review skill); .github/
keeps only CI-tool output-format shims (codex/pi/claude prompt files).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: make local-review a single-source-of-truth cross-agent skill
The repo already had parallel skills directories (.agents/skills/ and
.claude/skills/) drifting between agents. Consolidate local-review
onto one canonical file in .agents/ and symlink the .claude/ entry to
it so Claude Code and Pi share the exact same SKILL.md (Anthropic's
Skills format is supported by both, only the discovery directory
differs).
The canonical SKILL.md now points reviewers at
.github/review-prompt-shared.md as the policy source — same shared
prompt the GitHub auto-review workflows already use — so local
reviews and CI reviews stay in lockstep.
Codex CLI doesn't support repo-level slash commands (its prompts live
in ~/.codex/prompts/). For Codex parity, ship scripts/local-review.sh
which pipes the SKILL + shared policy into 'codex exec' (or 'pi -p'
as a uniform entry point). Update AGENTS.md to document the three
invocation paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: make all skills cross-agent — single source in .agents/, symlink .claude/
Turn every skill into a single canonical file under .agents/skills/ and
a symlink under .claude/skills/. Editing any one SKILL.md now updates
all three CLIs (Claude Code reads .claude/, Codex and Pi auto-discover
.agents/).
Per-skill resolution:
- local-review: already symlinked (prior PR #9037)
- rust-backend, svelte-frontend: identical content → symlink, no edit
- refine: only differed in user_invocable frontmatter → add to canonical
- native-trigger: .claude/ had a newer Step 17 (sidebar visibility)
missing from .agents/ → use Claude content as canonical
- commit: .claude/ embedded a Claude-specific Co-Authored-By trailer
the harness already injects automatically → drop from canonical, use
agent-neutral .agents/ version
- pr: generalize "Run /local-review" to "Invoke the local-review skill
(/local-review in Claude Code, $local-review in Codex, pi --skill
local-review in Pi)" and drop the Claude-specific "Generated with
Claude Code" attribution from the PR body template — the harness
that invoked the skill can add its own trailer if desired
- adding-a-trigger: was only in .claude/ → move to .agents/ canonical
- update-sqlx: was only in .agents/ → add .claude/ symlink
Also drop scripts/local-review.sh — wrapper is redundant now that all
three CLIs natively discover the skill from their respective
directories. Update AGENTS.md accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: share review policy across Claude/Codex/Pi via review-prompt-shared.md
All three reviewers now consume a single canonical policy document
(.github/review-prompt-shared.md) covering AGENTS.md compliance,
severity triage (P0/P1/P2), and a checklist for new public surfaces
(auth contract, module placement, half-finished pub fns, input
validation). Each tool's own prompt file shrinks to just its
output-format quirks, and each workflow concatenates shared +
tool-specific at runtime before invoking the model.
Drops the suppressive "Prefer at most 10 findings" / "Keep the review
high signal. If there is no clear issue, return no findings" wording
from Codex and Pi, which was clipping P1 and P2 findings (e.g.
half-finished pub fn, blocking I/O, wrong module placement).
Replaces it with severity triage so both reviewers report all P0/P1
and surface P2 when the diff invites it. Also makes AGENTS.md
authoritative for Codex (was CLAUDE.md, which is just @AGENTS.md in
this repo) and adds an explicit "new public function" checklist that
covers the missing-auth-check failure mode none of the three reviewers
flagged on the test PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: move test-coverage assessment to shared prompt, slim per-tool prompts
- Replace per-tool 'Reproduction instructions' with a single shared
'Test coverage assessment' section that asks each reviewer to
evaluate automated coverage (sufficient / thin / appropriate) and
describe what manual verification remains, if any.
- Slim per-tool prompts to the absolute minimum: just where to read
context, the comment header, severity tagging, and the Pi-only 'no
preamble' constraint. Everything else lives in the shared policy.
- Drop the model name from Pi's title ('Pi Review (DeepSeek V4)' →
'Pi Review') — the title's job is to let the bot find its own prior
comment when re-reviewing; the model is irrelevant to the reader.
The titles ('## Codex Review', '## Pi Review') stay because Codex and
Pi both post as github-actions[bot], so the heading is the only
discriminator the bot can use to find its own past comment in the
prior-discussion context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: scope test-coverage assessment to layers actually changed
Don't ask reviewers about integration tests on a frontend-only diff or
about playwright tests on a backend-only diff. The shared 'Test
coverage' section now lists categories (backend / frontend / CI-docs)
and tells the reviewer to skip the ones the PR does not touch — only
ask about Rust integration tests when backend handlers/workers/queues
were modified, only ask about frontend tests when components or state
machines were touched, and explicitly call out 'no automated tests
expected' for CI/docs/config diffs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: don't ask reviewers to flag missing frontend component tests
The Windmill frontend codebase doesn't generally test Svelte components
— existing tests cover pure-logic utilities only (flowDiff,
previousResults, copilot logic, dbtable queries, etc.). Asking
reviewers to flag every new component for lacking a test would produce
noise inconsistent with the established convention. Limit the
frontend test-coverage check to new pure-logic utilities (files that
would naturally have a sibling *.test.ts).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: point local-review skill at the shared review policy
Codex flagged (and Pi confirmed on its second pass) that slimming
.claude/review-prompt.md to output-only broke the local-review skill
contract — the skill still told Claude to read only that file for the
review criteria, so /local-review would no longer apply severity
triage, the public-surface checklist, or AGENTS.md compliance.
Update the skill to read .github/review-prompt-shared.md as the policy
source and .claude/review-prompt.md only for Claude output preferences.
Also align the local output format with the severity-tag convention
used by the workflow reviewers, and replace the lingering
'CLAUDE.md compliance' wording with 'AGENTS.md compliance'.
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(flows): don't bubble error when continue_on_error is on the last step
When the last step of a flow (or branch/forloop) failed with continue_on_error
or skip_failures enabled, should_continue_flow resolved to false (because the
flow was at its last step), and the flow was completed with success=false.
This made parent flows / subflows treat the run as a failure even though the
user explicitly asked to continue past errors.
Detect this case and set success=true so the failure is captured in the
result but not propagated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: explain why success is overridden post should_continue_flow
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: re-review on push, thread prior PR comments into reviewer context
- Add 'synchronize' to all three review workflow triggers so each push
to a PR branch re-runs Claude/Codex/Pi. Existing
cancel-in-progress concurrency groups ensure only the latest push's
review actually executes.
- Fetch the most recent up to 20 PR comments before each review and
inject them into the prompt context so the reviewer can recognize
its own previous review, focus on what changed, and avoid repeating
findings the human already addressed.
- Update the three review prompts (Claude, Codex, Pi) to instruct the
reviewer to honor the prior-discussion section when present.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: bump codex CLI to 0.128.0 for gpt-5.5 support
Codex 0.117.0 rejects the gpt-5.5 model with 'requires a newer version
of Codex'. 0.128.0 is the current stable release on npm.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: limit synchronize re-trigger to pi review only
Re-running Claude and Codex on every push gets expensive fast on busy
PRs. Pi (DeepSeek-V4) is cheap enough to re-run per push, while
Claude/Codex remain on opened/ready_for_review and re-trigger via
slash commands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: install bubblewrap for codex sandbox; stream pi progress in logs
- Codex's vendored bwrap fails to set up loopback on some ubicloud
runners, leaving codex unable to read any local files. Install the
system bubblewrap package before running codex so its read-only
sandbox works reliably.
- Switch pi to --mode json and pipe events through jq to surface
agent/turn boundaries and tool calls live in the GitHub Actions log,
matching codex's progress visibility. Final assistant text is
extracted from the saved event log into pi-final-message.md for the
PR comment.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: drop bubblewrap install, use codex -s danger-full-access
Codex's read-only sandbox uses bwrap which fails to set up loopback on
some ubicloud runners. Rather than apt-installing bubblewrap, switch to
the no-sandbox mode for parity with how Pi and Claude already operate
in the same workflow — runner is ephemeral and we trust the codex
prompt the same way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: bump codex review model from gpt-5.4 to gpt-5.5
gpt-5.5 is positioned as the agentic successor to gpt-5.4 — same
per-token latency, fewer tokens to complete Codex tasks, and
explicitly stronger at holding context across large systems and
multi-tool reasoning, which matches the PR review workload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: add Pi+DeepSeek-V4 review and slash command dispatcher
Auto-reviews now fan out to Claude (Opus), Codex (gpt-5.4), and Pi
(DeepSeek-V4-Pro) on PR open/ready. PR comments support /review (all
three), /codex, /pi, /claude with optional extra context appended to
the prompt. All review workflows now substitute EE code before review
and gate the auto-trigger path on org membership of the PR author.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: fix command parser whitespace, align checkout v5, broaden PR perms
- Trim leading/trailing whitespace from comment first line so /review
with leading space parses correctly (caught by Pi review)
- Standardize EE checkout step on actions/checkout@v5 across all three
review workflows (caught by Pi review)
- Bump pull-requests permission to write to satisfy GitHub's PR
comment endpoint when issues=write alone is rejected
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] fix(autoscaling): consider dedicated workers in scale decisions
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update ee-repo-ref.txt
* [ee] fix(autoscaling): mirror worker tag precedence (worker_tags wins)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 862d487032efe30d1e4a3cd0a1ed7169500c4cd9
This commit updates the EE repository reference after PR #556 was merged in windmill-ee-private.
Previous ee-repo-ref: cf87e9dcef2e95b1834b3f5c154209defc5a9ca2
New ee-repo-ref: 862d487032efe30d1e4a3cd0a1ed7169500c4cd9
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>