* fix: prevent variable push from corrupting is_secret variables
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(cli): unit-test looksLikeWorkspaceCiphertext shape detection
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): scope is_secret downgrade to single-file push, not sync push
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): warn when variable push stores a secret value as already-encrypted
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): route workspace-resolution and auth diagnostics to stderr
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(cli): rephrase comments to describe current behavior, not history
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ext-jwt): reject external JWT auth for non-existent workspaces
External JWTs are validated (not generated) on our side and never revoked
by us. The usage-tracking upsert into unique_ext_jwt_token ran
unconditionally, so a token carrying a workspace_id whose workspace no
longer exists kept refreshing its row on every presentation — surfacing
as a "new token" in the superadmin external-JWT view.
Gate jwt_ext_auth on the requested workspace existing (EE companion). When
it does not, auth fails (token is unusable) and no usage row is written.
The check is existence-only and intentionally ignores the soft-delete
flag, so deleted-then-restored workspaces keep working.
Bumps ee-repo-ref.txt to the EE companion commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(ext-jwt): cache workspace-existence lookups in jwt_ext_auth
Bumps ee-repo-ref.txt to the EE companion commit that caches the
workspace-existence check added in the previous commit, so a token aimed
at a missing workspace no longer hits the DB on every request (auth
failures aren't cached upstream).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to ac1f6f666f36141cb6ba6f8eaa614821a90464ad
This commit updates the EE repository reference after PR #626 was merged in windmill-ee-private.
Previous ee-repo-ref: e23fa03ec16909c127e8ecf0855595911c29512d
New ee-repo-ref: ac1f6f666f36141cb6ba6f8eaa614821a90464ad
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Follow-up to #9727. The orphan cleanups (cleanup_job_perms_orphaned and
cleanup_job_result_stream_orphaned_jobs) deleted at most one 100k batch per
monitor iteration. Each statement stays short and lock-light, but a single
batch per ~30s cycle caps the drain rate at ~100k/30s, so a large one-time
backlog (tens of millions of rows) takes ~hours to clear.
Loop the batched delete up to ORPHAN_CLEANUP_MAX_BATCHES (10) times per cycle,
stopping early once a batch deletes fewer than ORPHAN_CLEANUP_BATCH_SIZE rows.
Each DELETE remains bounded (≤100k, short locks, no long single statement),
while per-cycle throughput rises to ~1M rows so backlogs drain ~10x faster.
The per-cycle cap keeps monitor_db responsive.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(monitor): hash active-root exclusion in retention delete
The expired-job retention delete (delete_expired_jobs_batch) excluded jobs
belonging to still-active root flows with
`COALESCE(j.root_job, j.flow_innermost_root_job, jc.id) != ALL($3)`. That
ScalarArrayOp is evaluated per candidate row as a linear scan of $3, so cost
grows with the number of active root jobs.
Express the exclusion as `NOT IN (SELECT u FROM unnest($3) u WHERE u IS NOT
NULL)` instead. The subquery form lets Postgres build a one-time hashed SubPlan
and apply it as a filter on the ordered index scan, giving O(1) membership per
candidate while preserving the `ORDER BY completed_at ASC LIMIT` early
termination. The `u IS NOT NULL` guard sidesteps NOT IN's null-trap semantics
($3 holds non-null PK ids).
Measured on a 2M-row synthetic v2_job_completed (batch LIMIT 20000, 5-run min):
active roots | != ALL (before) | NOT IN hashed (after)
-------------|-----------------|----------------------
100 | 108 ms | 104 ms
1000 | 168 ms | 105 ms
10000 | 719 ms | 131 ms
Both forms return identical row sets (verified via EXCEPT, 0 diff). Neutral at
small active-root counts, ~5.5x faster when many flows are active.
Relates to WIN-2088
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(monitor): apply hashed active-root exclusion to log_cleanup mirror
windmill-api-settings/log_cleanup.rs::delete_expired_jobs_batch carries a
byte-identical copy of the retention delete and shared its prepared-query
cache. Updating only monitor.rs removed that shared cache entry and broke the
SQLX_OFFLINE build of the mirror. Apply the same NOT IN (hashed SubPlan)
rewrite so both copies converge on one cached query and the mirror gets the
same speedup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The job_perms/job_result_stream_v2 orphan cleanups in monitor_db used
`NOT IN` anti-joins, `RETURNING job_id` + `fetch_all` (loading every deleted
UUID into memory) and no batch limit. On high-throughput instances these
tables can accumulate tens of millions of orphaned rows, so a single execution
ran for ~298s; because monitor_db awaits each iteration, the cleanup ran
effectively continuously, saturating DB I/O and starving audit partition
creation.
Rewrite both deletes as bounded `NOT EXISTS` anti-joins selecting `ctid` with
a LIMIT 100000, executed via `.execute()` (using rows_affected instead of
fetch_all). Each run is now fast and bounded, while the 30s monitor cadence is
preserved so the tables keep draining promptly.
Fixes WIN-2088
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The monitor loop runs ~25 periodic tasks under a single join!, so any one
stuck on a non-DB await (statement_timeout only bounds DB statements) freezes
the whole loop indefinitely — silently halting audit-partition creation. Once
the missing partition's date is reached, audit inserts fail; because login
writes its audit row in the same transaction, that poisons the login tx and
locks every user out.
- Wrap monitor_db in a 600s timeout (> statement_timeout) so a stuck task can
no longer freeze the loop; report a critical error and continue on timeout.
- After creating partitions, verify the lookahead window is actually covered
and raise a critical alert naming any missing partitions, turning a silent
latent outage into an early page.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(apps): sandbox published & raw apps with a scoped embed token
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: point ee-repo-ref at embed-token EE commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(apps): allow top-navigation from the sandboxed app iframe
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(apps): share app localStorage across apps via the embedder
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(apps): publisher disable-sandbox option with per-version viewer consent
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(sqlx): cache for disable-sandbox queries
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref to disable-sandbox EE commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): always sandbox the served raw-app wrapper + viewer fixes
The raw-app wrapper served by get_raw_app_data now always carries
`CSP: sandbox`. The publisher "disable sandbox isolation" opt-out is applied
entirely on the viewer side, which (after per-version consent) builds its own
same-origin blob wrapper — so the backend-served document stays isolated
regardless of how it is reached, never via a relaxed real-origin URL.
Also:
- CORS on the global /apps_u mount so the opaque viewer can load custom-path
public apps cross-origin.
- Reject runnable-bridge messages unconditionally until the iframe is bound.
- Relay the viewer's in-app hash up to the embedder address bar so deep links
stay shareable (hash only; embedder keeps its own pathname).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(apps): render public raw apps single-iframe (drop embed token)
Public raw apps now render directly on the real origin with a single
opaque bundle iframe and the page credential, instead of the opaque
viewer + scoped-token indirection. The author bundle stays isolated in
its own opaque iframe (CSP-sandboxed); low-code apps, whose code runs in
the viewer frame, keep the opaque viewer + scoped token.
embed_token now reports raw_app and skips minting a token for raw apps;
the access check still gates visibility.
Also set disable_sandbox: None in the remaining Policy constructors so
the full feature build (all_sqlx_features, enterprise, license) compiles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: bump ee-repo-ref to single-iframe raw-app EE commit
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(apps): grandfather existing apps as legacy-unsandboxed + authed-only consent
Existing apps are stamped by migration as `legacy_unsandboxed` so they keep
running same-origin on upgrade — no breakage and no consent prompt. New apps are
sandboxed by default; re-deploying an app clears the flag.
The publisher `disable_sandbox` consent prompt is now shown only to authenticated
viewers — an anonymous viewer has no session to expose, so the prompt was
meaningless friction.
embed_token reports `legacy_unsandboxed` and `authed`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: bump ee-repo-ref to legacy-unsandboxed EE commit
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(apps): deploy-time migration prompt for legacy-unsandboxed apps
On the first re-deploy of a grandfathered (legacy-unsandboxed) app, the
publisher must explicitly choose: enable sandbox isolation (the flag is
cleared → the app becomes sandboxed) or keep running without isolation
(→ disable_sandbox, with per-version viewer consent). updatePolicy() no
longer carries the legacy flag through a deploy, so the choice is what
sticks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(apps): disable the sandbox-isolation toggle until the app is deployed
The Deploy-drawer "Disable sandbox isolation" toggle called setPublishState()
— which updates the app by path — even before the app was first deployed, when
the path is empty, throwing an error. Guard it with disabled={!savedApp},
matching the adjacent visibility toggle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(apps): sandbox the in-workspace low-code app viewer in an opaque iframe
Extend the opaque-origin iframe isolation to the logged-in /apps/get viewer.
/apps/get becomes an embedder that keeps the workspace chrome + Edit button and
renders the app inside a cookieless, chrome-less /app_embed viewer route, handed
a scoped embed token minted from the member's session. The app frame runs in an
opaque origin (no allow-same-origin), so it cannot reach the member's session
cookie or window.parent.
- apps.rs: get_app_embed_token_for_path (authed, by-path, scope + RLS gated);
mint_app_embed_token grants a path-scoped apps:read:{path} so the viewer can
load its own app definition and no other
- lib.rs: CORS on /apps (bearer-token only, no cookies) for the opaque viewer's
by-path reads
- new /app_embed/[workspace]/[...path] viewer route (private analog of /public)
- PublicAppFrame: viewerUrl prop to point the opaque iframe at the viewer route
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(apps): unify in-workspace app viewers on the shared sandboxed path
Route every in-workspace app display (low-code and raw) through the same
PublicAppFrame -> PublicApp machinery as the public viewer, so the sandbox /
legacy-unsandboxed / disable-sandbox-consent behavior is identical on every page.
- new InWorkspaceAppViewer renders both app types via PublicAppFrame; /apps/get
and /apps_raw/get become thin wrappers over it
- /apps_raw/get previously rendered RawAppPreview directly (always isolated, with
no legacy-grandfathering or consent handling); now consistent with the rest
- retire the legacy same-origin raw viewer /apps/get_raw/[version] and re-point the
apps-list row to /apps_raw/get; remove the dead /apps_raw/[ws]/[version] route
- load the raw bundle secret in the shared viewer (getAppByPath doesn't return it)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): address PR review feedback (scope + policy hardening, nits)
- require handler-level apps:read on list_apps / list_search_apps so a scoped
embed token cannot read app definitions through the list endpoints. The route
layer treats apps:run as satisfying read; the handler check (which does not)
closes the gap.
- treat legacy_unsandboxed as backend-owned: strip any client-provided value in
create/update so it can only be set by the grandfather migration, not the API.
- document mint_app_embed_token's caller-verifies-access contract.
- use Button's declared onClick prop for the consent action (was onclick, which
fell into the rest-spread and bypassed the component's click handling).
- test: lock that the embed scopes cannot satisfy domain-level apps:read.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(apps): document embed-token endpoints in openapi + fix doc nit
Second-round review nits:
- add the three app embed-token endpoints (apps/embed_token/p/{path},
apps_u/embed_token/{secret}, and the EE apps_u/embed_token_by_custom_path) plus
the EmbedTokenResponse schema to openapi.yaml; note .html on get_data
- mint_app_embed_token doc: "Both" -> "All" (it lists three call sites)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(apps): bound embed-token scopes to the caller's own
The embed-token mint now enforces ensure_scopes_within_caller, so the
minted scope set is always within the calling credential's own scopes
(a no-op for regular unscoped sessions). Adds a unit test locking the
boundary and documents the contract on mint_app_embed_token.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): raw-app ctx in external embeds + page credential in direct render
- RawAppPreview: engage the storage relay only in opaque frames (probe Web
Storage instead of just window.parent), so a public raw app embedded in an
external iframe hydrates ctx/storage directly; add a relay-timeout fallback
so an unresponsive parent can never stall the ctx handshake.
- PublicAppFrame: in direct render, expose the page's own bearer credential
through the AuthToken context (JWT public URLs), matching the previous
route behavior; opaque-viewer mode keeps the embed token.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(apps): sandbox isolation UI polish + COI embed support for raw apps
- Deploy drawer: move the sandbox toggle out of "Public URL" into its own
"Sandbox isolation" section (the setting applies to every viewing surface,
not just the public URL), with positive phrasing, visible helper text, and
state-aware alerts (warning when disabled, info for pre-isolation apps).
Toggling it now toasts its own message instead of the login-mode one.
- Extract the deploy-time migration prompt into a shared
LegacySandboxMigrationModal built on the common Modal component, and wire
it into the raw app editor header too (it previously had no prompt, so
re-deploying a pre-isolation raw app silently changed behavior).
updateRawAppPolicy now also drops the backend-owned legacy flag, matching
the low-code updatePolicy.
- Viewer consent prompt: use the common ConfirmationModal and show the app
path (new appPath prop) instead of the route pathname, falling back to
"this app" when the path isn't known yet.
- COI embeds: propagate the wm_coep opt-in to the raw-app wrapper document
and have the backend assert COEP require-corp on it when the flag is
present — required for the bundle iframe to load when the public app page
is embedded inside a cross-origin-isolated page. Previously this only
worked in dev because the Vite proxy injects the header; the production
response lacked it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(apps): app navigation parity across sandboxed and direct viewers
- Navbar component: same-app items relay query + hash to the embedder page
(which mirrors them onto the root URL, keeping its own pathname and
transport params), app items navigate the top page through a validated
wm_embed_navigate relay instead of the cookieless viewer iframe, and
external items keep opening a new tab. Selected-item detection now
recognizes the /app_embed viewer route and ignores transport params.
- Frontend-script `goto` and button `onSuccess: gotoUrl`: same-window
navigation goes through a shared appNavigateSameWindow helper that relays
to the embedder inside the opaque viewer (same-origin paths SPA-navigate,
http(s) URLs do a full load, other schemes rejected) and keeps plain
window.location everywhere else.
- /apps/get and /apps_raw/get: key the viewer by workspace/path so in-route
navigation fully remounts it — previously the URL changed but the app (and
in sandbox mode its path-scoped token) did not follow.
- wm_embed/wm_embedder_origin added to the reserved query params so they no
longer leak into the app's ctx.query.
- Raw apps: drop the sandbox attribute entirely for the unsandboxed
(grandfathered/consented) blob path, matching the pre-isolation viewer
exactly — the attribute added no isolation there and sandboxed popups
(e.g. OAuth flows).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): preserve grandfathered policy across updates + in-workspace viewer parity
Round of compatibility hardening so pre-existing apps behave exactly as
before on every surface:
- `legacy_unsandboxed` is now preserved across app updates unless the payload
explicitly clears it (`false`, sent by the editor's migration prompt and the
sandbox toggle). Unrelated update paths — CLI / git-sync redeploys,
publish-mode toggles, cross-workspace promotion — no longer silently drop
the grandfathering. Clients still can never SET the flag.
- The embed-token endpoints (secret, path, EE custom-path) read only the
sandbox-decision policy fields, leniently, and no longer mint a token for
raw / legacy / disable_sandbox renders: the token is only consumed by the
sandboxed low-code render, and minting for the others wrote a useless token
row per view and could fail the render for scope-restricted callers.
- In-workspace viewer parity with the pre-sandbox `/apps/get`: new
`inWorkspace` mode on PublicApp (no "Powered by Windmill" badge / user
overlay, no HTML-result approval gate, column flex wrapper, `hideRefreshBar`
honored again), and the page's query/hash are forwarded into the opaque
viewer so `ctx.query` / `ctx.hash` reach the app.
- Raw apps: `window.ctx` is always `{ctx, workspace}` again (anonymous viewers
of pre-existing bundles rely on `ctx.workspace`), and the runnable bridge's
job-id scoping now applies only to sandboxed renders (`gateJobIds`) — an
unsandboxed bundle holds the same credential as the bridge, so gating there
only broke pre-existing apps polling persisted or runnable-returned job ids.
- Document `disable_sandbox` / `legacy_unsandboxed` in the openapi Policy
schema; add a unit test for the lenient policy read.
- bump ee-repo-ref to the matching EE commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): keep share-link viewer credentials out of the isolated app context
The JWT path segment of authenticated share URLs is an embedder-side
credential, consumed only to mint the scoped embed token. Two transport
channels still copied it into the isolated frame where app-authored code
runs:
- the opaque viewer iframe src defaulted to window.location.href — the
public and custom-path routes now pass a sanitized viewerUrl (JWT segment
stripped, query/hash preserved, captured once so the hash relay does not
reload the iframe);
- document.referrer on the same-origin iframe navigation carried the full
embedder URL — both app iframes now set referrerpolicy="no-referrer"
(sandboxed renders only for the raw bundle iframe, keeping exact legacy
parity; nothing reads the referrer).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(frontend): drop unused import inherited from main merge
`slide` import in AssistantMessage.svelte (from #9539) turns `npm run check`
red on this branch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): redirect the removed raw-app viewer path to the unified viewer
The old same-origin raw-app viewer route (/apps/get_raw/{version}/{path}) was
removed in favor of the sandboxed unified viewer. Re-add a thin client route at
the old path that redirects stale bookmarks to /apps_raw/get/{path}, preserving
query + hash (the pinned version is dropped — the unified viewer shows latest).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): narrow embed-token scopes and base consent on browser session
- Embed token: resource access is metadata-only (list/type/exists) via a
`resources:run` marker — resource values (get/get_value/get_value_interpolated/
list_search) are no longer reachable. Job reads are by-id only: an `app_embed`
sentinel blocks the workspace-wide job enumeration/export routes (jobs/list,
list_filtered_uuids, queue/list, completed/list, queue/export) while by-id
result polling keeps working.
- disable_sandbox consent now gates on whether the browser holds any Windmill
session (cookie-only whoami) rather than workspace-scoped auth, so a viewer
logged into a different workspace is still prompted before a same-origin render.
- db-explorer: resolve the MySQL database name server-side (the metadata query
already falls back to DATABASE()) instead of reading the resource value
client-side; getTablesByResource derives the default db from the schema.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(apps): trim embed-scope and consent comments
Reduce duplication — state the resource/job route exclusions and the
workspace-session-vs-cookie rationale once at their source and reference them
elsewhere; drop contrast/justification phrasing. No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(apps): make app sandbox isolation opt-in (alpha)
Replace the disable_sandbox + legacy_unsandboxed policy pair and the
per-version viewer consent with a single positive `sandbox` opt-in flag.
Apps are unsandboxed by default (same-origin, full session — the
pre-isolation behavior), so existing apps are unchanged and no migration
is needed. Publishers opt an app into isolation from the deploy drawer,
flagged alpha.
- Policy.sandbox: Option<bool>; EmbedTokenResponse -> {token, expiration,
raw_app, sandbox}; mint an embed token only for sandboxed low-code apps.
- Drop the legacy-unsandboxed migration and the deploy-time migration
prompt; remove the consent modal and the browser-session probe.
- Deploy drawer: a single "Sandbox isolation" toggle (alpha), off by
default, shared by the low-code and raw editors.
- Bump ee-repo-ref to the companion EE commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): confine embed token to its intended user/folder/job routes
The embed token's broad read scopes spanned whole domains while the
matching routers are CORS-enabled for the opaque app iframe:
- users:read / folders:read were domain-wide, so the token could reach
users/list, users/list_usage, users/username_to_email/*, folders/list,
etc. Restrict to an app_embed-sentinel allowlist: only users/whoami and
folders/listnames; deny the rest of those domains.
- jobs:read allowed jobs/completed/export, missed by the job denylist.
Add it alongside jobs/queue/export.
Extend the embed-scope allow/deny test matrix to cover all of these.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(apps): align sandbox comments with the opt-in model
The consent prompt, deploy-time migration, and legacy-unsandboxed
grandfathering were removed when sandbox isolation became an opt-in
policy flag; update the comments that still described them so they
match the two-state (default-unsandboxed / opt-in-sandboxed) reality.
Comments only, no behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): confine embed-token job reads to runs the app launched
App component jobs are stamped `created_by = the viewer`, so an embed token
reads its own runs via the launched-by-viewer fast path. The token then also
inherited the viewer's broader job access (share links, folder ACLs, admin
RLS), letting user-authored app JS reuse it to read unrelated jobs by id. Stop
embed tokens at the fast path: only jobs the viewer launched, never those
merely visible to them. Return NotFound so the untrusted app can't probe
existence.
Regression test: an embed token reads its own launched job but is denied the
foreign job (result/logs/getupdate) an admin viewer's normal token can read.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): allowlist embed-token apps/jobs routes + scope run to the app
The embed token's apps:run/jobs:read reached more than a running app needs.
Replace the job denylist with strict per-domain allowlists on the app_embed
sentinel:
- Apps: only the app's own definition (apps/get/p/<path>) and the public
app-serving endpoints (apps_u/*). Denies workspace app inventory
(exists, custom_path_exists, list, list_paths*).
- Jobs: only the by-id poll routes the frontend JobLoader uses. Denies job
counts and the job_signature/resume_urls capability-minting routes (the
by-id reads remain confined to the app's own runs).
Drop unqualified apps:run from APP_EMBED_SCOPES; mint apps:run:<path> instead
and authorize apps:run:<requested path> first in execute_component, so the
token can only run its own app's components, not another app's.
Extend the embed-scope route matrix and add a path-scoped run unit test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(apps): clarify the sandbox toggle vs the on-behalf-of model
The deploy-drawer sandbox copy leaned on "session" in a way that collided
with the on-behalf-of permissioning right above it. Reword it to say the
toggle governs what the app's browser-side code can reach in the viewer's
browser — distinct from who its runnables execute as — and rename the label
to "Isolate the app from the viewer's browser session".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): path-scope embed-token S3 download to its own app
The apps_u/* allowlist also admitted apps_u/download_s3_file/<path>, whose
handler authorized any authenticated caller — so an embed token minted for app
A could download app B's S3 files via B's on-behalf policy. Add the same
path-scoped guard execute_component uses: download_s3_file_from_app now checks
apps:read:<path> first, confining the token to its own app. Other path-taking
apps_u routes are already covered (writes lack apps:write; embed_token/p
path-checks; public_resource is type-constrained).
Extend the path-scoping unit test to cover apps:read (download) alongside
apps:run (execute).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): path-scope public-app-by-secret read to the embed token's app
The apps_u/* allowlist admitted apps_u/public_app/<secret>, whose handler only
checked the viewer's read access — so an embed token minted for app A could read
app B's definition by secret (confused deputy via the viewer's identity).
get_public_app_by_secret now binds a scoped caller to the resolved app with
check_scopes(apps:read:<path>), confining it to its own app; unscoped sessions
and anonymous access are unchanged.
get_raw_app_data needs no binding (pure secret capability, no caller identity).
Document the full set of app-resolving handlers the path-scoped read covers.
Bump ee-repo-ref for the companion custom-path fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): preserve pre-sandbox behavior for db-explorer, edit link, jwt
Three behavior-parity fixes for non-sandboxed (existing) apps that the
sandbox-isolation refactor changed incidentally:
- DB-explorer MySQL table picker: when the connection can see multiple
non-system schemas, label the default db's tables unprefixed again. The
resource-value read was removed globally, so identify the default db from
the introspection script's `DATABASE() AS default_db_name` (carried on
SQLSchema.defaultDb) instead of guessing "the single schema key". Equivalent
to the prior resource.database match; editor-only (table picker).
- In-workspace Edit button: restore `?nodraft=true` on both /apps/get and
/apps_raw/get, so opening the editor from the viewer loads the deployed
version, not a draft.
- Custom-path (/a) viewer: restore the "could not authenticate user with jwt
token" toast when a path JWT fails to resolve a user, instead of silently
falling through.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): confine embed-token S3 downloads to the app's own keys/outputs
download_s3_file_from_app authorized any authenticated caller for any S3 key
(opt_authed.is_some() bypass). A sandboxed app's embed token carries the
viewer's identity, so app-authored JS could fetch arbitrary S3 keys readable by
the on-behalf identity, beyond the app's own declared keys or outputs.
Route app embed tokens through the same allowlist as anonymous viewers — the
app's declared allowed_s3_keys, or files produced by this app's own component
runs — instead of the authed bypass. The produced-files check is parameterized
by created_by (the embed viewer for a token, else anonymous) so a sandboxed
app's own S3 outputs still render while arbitrary keys are denied.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): let embed tokens cancel their own jobs; gate cancel to launcher
A sandboxed low-code app supersedes an in-flight component run on re-run by
canceling it, but the embed token only had jobs:read, so cancellation silently
failed and prior jobs ran to completion.
- Permit the by-id jobs_u/queue/cancel POST for app_embed tokens at the route
layer (the only write reachable through the existing by-id allowlist).
- Gate cancel_job_api: an app_embed token may cancel ONLY jobs it launched
(created_by == viewer). cancel_job_api had no other per-job ownership check,
so this also confines the token instead of letting it cancel any job by id.
- /app_embed now sets workspaceStore so cancellation targets the right
workspace instead of an empty/stale one in the cookieless iframe.
Add a shared has_app_embed_sentinel helper; cover cancel in the route matrix
and the jobs_read_auth integration test (own job cancelable, foreign denied).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): drop get_root_job_id from the embed-token job allowlist
Audit of the embed token's reachable job routes: get_root_job (jobs_u/
get_root_job_id) has no access check in its handler at all — it returns any
job's root-job id by id — and the app runtime never calls it. Remove it from
the by-id allowlist so the embed token can't probe a foreign job's flow lineage;
add a denied-route assertion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(apps): scope sandboxed-app localStorage per app
Sandboxed apps shared one localStorage store (one key on the real origin), so an
app could read or clobber another app's keys — and, with job ids stashed there,
reuse its embed token to read another app's job. Scope the backing store per app.
The embed-token endpoints now return the resolved app_path (EmbedTokenResponse;
not a new disclosure — the viewer already receives the path when it loads the
app). PublicAppFrame (low-code) and RawAppPreview (raw) key their backing store
by it: wm_apps_localstorage:<app_path>. Same app shares one store across its
public and in-workspace surfaces; different apps are isolated. Unsandboxed apps
are unaffected (real same-origin localStorage, as before).
Bump ee-repo-ref for the companion custom-path change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): scope embed access checks to embed tokens + key app storage by workspace
- Apply the path-scoped read/run checks on the public-by-secret read and the
component run path only when the caller is an app embed token, so other
caller types keep their prior access.
- Key the sandboxed app's backing client storage by workspace + path instead
of path alone, and return the resolved workspace from the embed-token
endpoints so the custom-path viewer can derive it.
- Show a clear message instead of an indefinite loader when the viewer route
is opened outside its embedder.
Bumps ee-repo-ref to 5b8476b.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): mint embed tokens only from the trusted embedder caller
An app embed token must not reach the embed-token mint endpoints; refresh
minting stays with the embedder session/JWT. Enforced at the scope route
layer and at the mint chokepoint, with a route-matrix regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(apps): support S3 upload and frontend-script S3 download in sandboxed apps
Sandboxed apps run with a scoped embed token (no cookie). Let the app's
S3 file-input upload and the frontend-script download({s3}) helper work in
that context: upload is reachable with apps:run and re-checked per-app at the
handler; the script download routes through the app-scoped apps_u endpoint
with the embed token instead of the cookie-authed job_helpers path. Default
(unsandboxed) apps are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: update ee-repo-ref to b0cb761bf9852974e571b2978032d310cc998517
This commit updates the EE repository reference after PR #600 was merged in windmill-ee-private.
Previous ee-repo-ref: e673c714a4618fdb72353a475f49c748e6016642
New ee-repo-ref: b0cb761bf9852974e571b2978032d310cc998517
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 (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>
The /api/health/status database check used `SELECT 1`, which succeeds
even on a read-only standby. After a PostgreSQL failover where the
primary becomes a secondary, the health check kept reporting healthy
while all writes failed with "cannot execute INSERT in a read-only
transaction", so Kubernetes liveness probes never restarted the pod.
Use `SELECT NOT pg_is_in_recovery()` instead: it returns true on a
primary and false on a standby, so a read-only replica is now reported
unhealthy. Result handling checks the returned bool (Ok(Some(true)))
rather than just query success.
Fixes WIN-2085
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-chat): workspace ai_skill table + CRUD API
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-chat): AI Skills workspace settings tab with SKILL.md upload
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-chat): advertise skills in global system prompt + read_skill tool
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai-chat): move custom skills into AI settings (paste or folder)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-chat): cap folder import (depth<=3, max 50 skills, confirm dialog)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(ai-chat): give import folder its own labeled subsection
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-chat): resolve svelte-check never-narrowing in skills preview
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address ai skills review issues
* fix: validate ai skills and reload workspace list
* fix(ai-chat): spec-align skill validation and cap skills per workspace
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-chat): reject duplicate skill uploads, audit skill names
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-chat): sync deref openapi specs with skill validation rules
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Flows with no defined inputs can produce a sparse schema (e.g. { order: [] })
that lacks the "type": "object" field. buildSchemaForTool spread this schema
into the tool parameters as-is, so the Anthropic API rejected the tool
definition with `400 invalid_request_error:
tools.N.custom.input_schema.type: Field required`. The existing fallback in
anthropic.ts only triggers when parameters is falsy, but the sparse schema is
truthy.
Default type:object before spreading the schema in buildSchemaForTool, and
backfill type/properties/required in FlowAIChat's getFlowInputsSchema as a
defense-in-depth measure.
Fixes WIN-2087
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: re-enforce per-path token scope on store rename, delete and interpolation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: enforce token scope on workspace export and resume-url minting
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: enforce per-item and runnable scope on trigger create paths
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: enforce app write scope before persistence and on rename
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: enforce scope containment on mcp oauth approval
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: scope mcp endpoint-proxy jwt to the proxied route
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: treat resource-linked variables and resources as covered by resource scope
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: only require variables:read for plaintext-secret workspace export
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: handle singlestepflow resume, reject empty mcp grant, scope var-skipped tarball
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switching workspaces created a new WebsocketProvider without destroying
the old one. The leaked provider kept reconnecting, causing alternating
websocket traffic between old and new workspace rooms and flickering in
the Live Activity sidebar.
Add a disconnectWorkspace() cleanup that destroys the provider and resets
connected/awareness state, call it at the start of connectWorkspace()
before creating a new provider (matching ScriptEditor.svelte), and run it
from an onDestroy hook so the provider is torn down on unmount.
Fixes WIN-2086
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): strip raw-app post-deploy diff noise (raw_app/lock/data)
The raw-app editor's Diff drawer showed a spurious deployed-vs-current
diff immediately after deploy, even with no edits: `raw_app: true`, a
server-recomputed inline-script `lock`, and an empty `data` mismatch.
These come from comparing the deployed app row (from getAppByPath) against
the editor's current value, which differ on server-managed fields the
editor never carries, on inline-script locks (recomputed at deploy, cleared
on edit), and on `data` (the deployed row omits an empty `data` while the
editor always carries the default `{tables: []}`).
Add `stripRawAppDiffNoise` (strip server columns, null inline locks,
canonicalize data) and apply it symmetrically to both diff sides in the
editor header. For the session/compare draft diff, the draft is stored flat
(files/runnables/data top-level) while the deployed row nests under `value`,
so add `canonicalRawAppDiffValue` (= appSourceToDraftValue + stripRawAppDiffNoise)
and route both sides through it in getDraftDiffValues. Both diff surfaces now
share the same normalizer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): use canonicalized current value in deploy-drawer raw-app diff
The Deploy drawer's "Diff" action still built the current side inline from
raw editor state, bypassing stripRawAppDiffNoise — so inline-script `lock`
and data-shape noise could resurface via Deploy → Diff even though the
top-level Diff button was already fixed. Route it through `currentDiffValue`
(and strip the savedApp fallback) so both entry points behave identically.
Addresses Codex review finding on PR #9706.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: ignore NotFound errors when deleting log files from object store
Periodic and manual log cleanup delete log files from instance object
storage. S3's DeleteObjects silently ignores missing keys, but GCS
returns a 404 for each individual delete, which the object_store crate's
default delete_stream surfaces as Error::NotFound. This produced noisy
error/warning logs on every cleanup cycle even though the cleanup
succeeded (DB records are removed regardless).
Treat a NotFound delete as a successful no-op in both delete handlers:
- monitor.rs: skip logging NotFound errors
- log_cleanup.rs: count NotFound as deleted instead of an error
Fixes WIN-2081
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: report 404 (already-absent) count in object store log cleanup
Track delete calls that returned 404 (object already absent) separately
from real deletes so operators can see how many of the attempted deletes
were no-ops, instead of those numbers silently folding into s3_deleted.
- monitor.rs: emit a final info summary per cleanup cycle:
"N deleted, M already absent (404), K failed" (only when work occurred)
- log_cleanup.rs: add s3_not_found to LogCleanupProgress (serde default for
backward-compatible deserialization of in-flight rows), thread it through
s3_bulk_delete and all call sites, and log a final summary on release
- openapi.yaml + generated client + ObjectStoreConfigSettings.svelte:
surface the 404 count in the manual cleanup status UI
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: import ObjectStoreError directly from object_store_reexports
The object_store_reexports module already re-exports object_store::Error
under the name ObjectStoreError, so `Error as ObjectStoreError` failed to
resolve (no `Error` in that module). This compiles only behind the
parquet feature, which the local dev `cargo watch` doesn't enable, so it
was caught by CI's full-feature check rather than locally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(oauth): restore bring-your-own CC token URL override
Re-add the optional resource-level token URL field for client-credentials
connections, sent only with the caller's own client_id/secret. Updates the
connect/create_account request schemas and bumps the EE ref.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(oauth): keep openapi-deref unchanged from main
The dereferenced specs are not regenerated per-PR (already stale on main,
CI only lint-validates them). Revert the incidental full regen so the PR
diff stays focused on openapi.yaml.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(oauth): host-pin CC token URL override server-side
Add is_instance_templated_cc so the EE handlers can reject a bring-your-own
token URL override for {instance}-templated providers (defense in depth for
direct API callers). Bump the EE ref.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(oauth): serve cc_token_url in deref specs, enforce CC grant gate
Add cc_token_url to the dereferenced OpenAPI artifacts served at /openapi.yaml
and /openapi.json so generated clients see the new field (kept to a focused add
rather than a full regen). Bump the EE ref for the grant-gate enforcement.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to de49fda2320504ad9e7d2d31c7033d71dbf6ca43
This commit updates the EE repository reference after PR #625 was merged in windmill-ee-private.
Previous ee-repo-ref: a939228d0314c21937687d43c8ef354bdc87c40e
New ee-repo-ref: de49fda2320504ad9e7d2d31c7033d71dbf6ca43
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* ci: add path-gated AI agent integration tests workflow
Runs integration_tests/ai_agent_tests against real LLM providers
(Anthropic/OpenAI/Google) only when AI-agent backend code or the tests
change, since runs make paid LLM calls. Adds a conftest fixture that
skips provider-parametrized cases whose API keys are absent, so CI
exercises only the providers it has secrets for.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: add path-gated ai_evals global-mode smoke workflow
Runs the global AI chat eval (global-test1) across one cheap model per
provider (Anthropic/OpenAI/Google/DeepSeek) only when the eval harness or
copilot chat code change, since runs make paid LLM calls. Builds Windmill
CE from source as the AI proxy; global tools/drafts run in the Vitest
bridge. Gates on the deterministic draft pipeline (run succeeded +
produced a draft + used write_script), not the variable LLM judge score.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: run AI smokes on PR ready-for-review instead of every push
Switch the pull_request trigger from `synchronize` (every commit) to
`ready_for_review`, with a job guard skipping draft PRs, so the paid LLM
runs only fire when a PR is marked ready to merge (plus push-to-main and
manual dispatch).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai_evals): lazily load cli mode so non-cli evals skip the cli toolchain
The entrypoint eagerly imported modes/cli, which pulls the wmill CLI
guidance modules and their JSR deps (@cliffy/*). Global/flow/script/app
runs then crashed with "Cannot find module '@cliffy/ansi/colors'" when
the cli workspace deps were not installed. Import createCliModeRunner
dynamically inside runCliBenchmark instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(ai_agent): raise low max_completion_tokens to OpenAI's 16 minimum
OpenAI's /v1/responses rejects max_output_tokens < 16 with a 400, failing
test_low_max_tokens for openai. 16 still exercises a truncated response.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: run ai_evals workflow on Node 22 for the frontend undici 8.x dep
The Vitest bridge loads frontend/node_modules/undici@8.x, which requires
Node >=22.19; Node 20 failed with "webidl.util.markAsUncloneable is not a
function" when loading vitest.config.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai_evals): run frontend evals autonomously + give global-test1 more turns
Frontend evals (flow/script/app/global) ran the production chat prompt, which
assumes an interactive human — so cheaper models burned their turn budget
asking for confirmation, waiting for approval, or presenting a plan, sometimes
hitting maxTurns without producing a draft. Append a shared autonomy note in
baseEvalRunner (the path all frontend modes share, mirroring cli mode): act
directly on clear requests; only ask on genuinely ambiguous ones (preserving
the askUserQuestion cases). Also raise global-test1's maxTurns 8 -> 10 so a
model that over-explores still converges.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci(ai_evals): watch draft/prompt deps outside copilot/
The global eval runs production frontend code in-process, so the smoke's
behavior depends on files outside frontend/src/lib/components/copilot/**:
the draft model (userDraft.svelte.ts, userDraftDbSyncer.svelte.ts), script
inference (infer.ts), and the chat system prompts ($system_prompts ->
system_prompts/auto-generated). Add them to both push and PR path filters so
a change there actually triggers the smoke that gates on draft production.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: skip direct provider tests without credentials
* feat: add ai evals skip judge flag
* fix: simplify ai evals ci gate
* fix: simplify ai evals smoke gate
* fix: handle ai eval workflow triggers
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: add global-mode path-selection eval cases with seeded user
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(copilot): guide global-mode path selection with injected folder list
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(copilot): tailor global-mode folder guidance for workspace admins
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(copilot): type folders_read; isolate global-eval user from store
Addresses PR review:
- Add folders_read to the User/whoami openapi schema and UserExt; the global prompt builder and eval harness now read it typed instead of via inline casts (regen the client to pick it up).
- prepareGlobalSystemMessage takes an explicit user; the eval harness passes it rather than mutating the process-global userStore, removing the concurrency race (path cases no longer need --verbose).
- Rewrite the path-selection case comment as a current invariant.
- Add buildFolderGuidance unit tests in core.test.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: round worker usage occupancy up to 5-minute chunks
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 8b12fa14ef7969e948169eadf1bf672d7928e5b1
This commit updates the EE repository reference after PR #624 was merged in windmill-ee-private.
Previous ee-repo-ref: 168498974150ff309658b2da43bce8444f13a765
New ee-repo-ref: 8b12fa14ef7969e948169eadf1bf672d7928e5b1
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(frontend): deploy full script/flow draft from AI chat via shared module
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): drop non-persisted priority/timeout from flow draft deploy
The flow branch of the shared deployDraft set `priority`/`timeout` on the
create/update body, but the backend does not persist those fields on flows
(a direct API write returns them as null). Remove the dead fields and the
unit-test assertions for them; the flow deploy still carries every config
field the backend actually stores (tag, dedicated_worker, …).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): chat deploy resolves draft storage path (honor chosen path)
The chat addresses drafts by their display/chosen path, but a draft_only item
created in the editor lives at a synthetic `u/{user}/draft_{uuid}` storage key
(chosen path held in the draft value). The shared deployer reads the draft via
getScriptByPath/getFlowByPath at the path passed, so passing the chosen path
404'd. Resolve to the storage path via getGlobalDraftStoragePath before
delegating; the deployer then deploys at the draft's own `path`. Regression from
the deploy-unification: the old builder read the already-resolved draft and
deployed at the chosen path directly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): chat raw-app deploy honors the draft's chosen path
The raw-app branch deployed at the path the chat was addressed by (args.path),
which for an editor-created draft_only raw app is the synthetic
`u/{user}/draft_{uuid}` storage key, not the chosen path. Resolve the storage
path and read the chosen path from the backend raw_app draft's `draft_path`
(confirmed shape: getAppByPath{getDraft,rawApp}.draft.draft_path), then create/
update there — mirroring the script/flow storage-path resolution. Content still
comes from the flat AppDraftValue, which the editor and chat both use.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): flush live draft before chat deploy; narrow raw-app catch
Addresses review feedback on the AI-chat deploy:
- Codex P1: script/flow deploy delegates to the shared deployer, which re-reads
the persisted DB draft. An open editor's edit may still be parked in a
debounced/disabled autosave, so the deploy could publish a stale draft and the
post-deploy draft delete could drop the unsaved edit. Flush the draft's
UserDraftDbSyncer key before delegating (always saves, like Ctrl/Cmd+S, since
the user explicitly asked to deploy).
- Cubic P2: the raw-app draft_path lookup caught all errors and fell back to the
storage path, masking real failures (network/5xx). Only fall back on 404;
re-throw other errors so the deploy aborts instead of deploying to the wrong path.
Adds tests for both; updates the existing raw-app deploy tests to mock getAppByPath.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): flush raw-app draft before reading draft_path on chat deploy
Codex P1 follow-up: the raw-app branch derives the deploy targetPath by re-reading
draft_path from the persisted backend draft, but — unlike script/flow — didn't
flush first. An editor rename mirrored into draft_path can still be parked in a
debounced/disabled autosave, so an immediate chat deploy could read a stale
draft_path and deploy to the old path. Flush the raw_app draft key before the
getAppByPath read, mirroring the script/flow fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): abort chat deploy when pre-deploy draft flush conflicts/fails
Codex P1 follow-up: the pre-delegation UserDraftDbSyncer.flush() resolves even
when the save recorded a conflict (server has a newer version) or failed
(network/5xx) — it does not throw. The deploy would then re-read a stale or
conflicting persisted draft and publish it. Add flushDraftOrThrow(): after flush,
check getConflict() and getState().state === 'failed' and abort with a clear
message. Used by both the script/flow and raw-app deploy paths.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: default instance db name to dt_/dl_ workspace scope
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: cap instance db name at 63 chars and add unit tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add file attachments to the global AI chat
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: add folder linking and file-type icons to chat attachments
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: persist linked files, add @-menu file tree, and polish chat file UI
Persistence (survive reload, scoped to session.id):
- IndexedDB store (attachedFilesDB) holding Blob snapshots (every browser)
and re-grantable File System Access directory handles (capable browsers)
- restore on session activation; re-grant locked handles on the next send;
flush in-memory items when the session persists; GC on session delete
- capability via feature-detection (fsAccess), never UA sniffing
- folders auto-refresh (live re-enumerate + reconcile) on each send
@-mention file picker:
- Files branch in ChatContextPicker (new DrillPicker architecture); a linked
folder's files render as a nested directory tree, picking inserts @filename
- attached-file mentions highlight in the input just like context mentions
UI polish:
- file/folder chips reuse the context-element chip style (icon -> X on hover)
- file + context badges sit above the fork/draft bar
- disabled dropdown items can surface an explanatory tooltip (DropdownV2Inner)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: deepen the attached-files store — folders as first-class objects
Two seam fixes from an architecture pass, no behaviour change:
- addFolder(dirHandle) now enumerates internally (same junk-filtered walk
used on restore/refresh), so callers never pre-enumerate. The dead
drop-walkers (collectDroppedEntries, filterFolderPickerFiles) are deleted;
isIgnoredPath/MAX_FOLDER_FILES move next to enumerateDir in fsAccess.
- The store exposes `folders` (name + aggregate status + children) and
`standalone` as derived views, so the bar, the @-menu picker, the folder
chip and the system-prompt roster stop re-grouping the flat row list and
re-deriving folder status. Placeholder rows (isFolderRoot) become an
implementation detail; the roster renders a locked folder as one line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: drop the redundant context-badge row in the global chat
In GLOBAL mode selected context already appears as a highlighted @mention
in the input (deleting the mention deselects), so the hoisted badge row
above the chat duplicated it. File chips keep their row — attachments
aren't represented in the input.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: harden attachment edge cases found in review
- requestReadPermission/queryReadPermission never reject (the spec rejects
with SecurityError when user activation is missing — now mapped to
denied/prompt), and sendRequest wraps attachment upkeep in try/catch, so
a permission hiccup can never silently swallow a Send.
- regrantLocked expands before dropping the locked placeholder: when the
re-granted directory is gone from disk, the folder now shows
"unavailable" instead of vanishing into a zombie that resurrects locked
on the next reload.
- addFolder: re-picking a locked/unavailable folder relinks it (natural
recovery gesture); a genuine second folder with the same basename gets a
visible "already linked" rejection instead of a silent no-op.
- fileEngine: readFile clamps its byte slice to maxChars*4 before decoding
and streamLines caps its per-line buffer, so newline-sparse files
(minified JS, single-line JSONL) can't materialize unbounded strings;
corrected the scan-cap comment's claim about catastrophic backtracking.
4 new unit tests (41 total).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: surface folder-picker failures instead of swallowing them
`pickDirectory` caught every `showDirectoryPicker` rejection and returned
undefined, so a real failure (an enterprise/browser policy blocking the File
System Access API, a lost user-activation, …) was indistinguishable from a
no-op — the picker just silently never opened. Now only `AbortError` (user
dismissed the dialog, or CDP intercepted it under automation) is treated as a
cancel; anything else is rethrown and `linkFolder` surfaces it as a toast.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: support folders in browsers without the File System Access API
Folders can now be added in every browser, not just Chromium. Where the File
System Access API is absent (Firefox/Safari), a dropped or picked folder's files
are snapshotted into the browser (via a webkitGetAsEntry drop-walk or a
`webkitdirectory` input) instead of linked as a live handle, and grouped/displayed
identically to a File System Access folder. The dropdown item reads "Link folder"
when a live link is possible and "Add folder" otherwise, with a tooltip pointing
to Chrome/Edge for a live link.
Snapshot folder children persist their `folder`/`relPath`, so they regroup into
the same folder chip on reload.
Removes the arbitrary file-count caps (500 per folder, 100 total) — only the
browser's memory / IndexedDB quota now bound a folder. Junk paths
(node_modules/.git/dist/dotfiles) are still skipped, folder-contents only, so an
explicitly attached standalone dotfile is kept.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address review feedback — index-race guard + read_file line numbers
Both automated reviewers flagged two issues on the attached-files feature:
- (P1) Stale async indexing could corrupt a newer file. `#indexFile` applied
its unawaited `buildLineIndex` result by display name, so if a row's file was
swapped while indexing was in flight (remove + re-add a same-named file, or a
folder refresh re-indexing an edited file) the stale result stamped the wrong
lineIndex/lineCount — and `read_file` then sliced the new Blob with old
offsets. Now patched via `#patchFile`, which applies the result only while the
row still holds the exact file object that was indexed.
- (P2) `read_file` promised "line-numbered context" but returned raw text. It now
prefixes each line with its absolute 1-based number (`<n>→<content>`), matching
the tool contract; `numberLines` lives in fileEngine and is unit-tested.
Adds regression tests: a deterministic stale-index race test (controlled
buildLineIndex ordering) and numberLines coverage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address re-review nits — read_file pagination + searchFiles regex state
- read_file: when the maxChars cap truncated a window short of its requested
end line, the pagination note still reported the full range and gave no/wrong
resume point, so the model couldn't reach the unread lines. The note now
reports the last line actually returned and resumes at the next unread line
(advancing past a single over-long line rather than re-truncating it forever).
- searchFiles: reset `regex.lastIndex` before each `.test()` — a caller-supplied
`g`/`y` flag makes test() stateful and would silently drop matches. Not
reachable from the current caller, but searchFiles is exported.
Adds regression tests for both.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: keep an emptied live folder linked and refreshing
A live (File System Access) folder carried its directory handle only on its child
file rows. When the folder was emptied on disk, refreshFolders/#reconcileFolder
removed the last child — dropping the only handle-bearing row — so the folder
vanished from the chip bar AND was never re-enumerated again (files added back on
disk weren't picked up until a reload). #expandFolder had the same gap on restore.
Now #ensureFolderRow leaves one handle-carrying placeholder row when a folder has
no readable children (keeps the chip visible and the live source alive), and drops
it once children return; refreshFolders collects sources from placeholder rows too,
and readyFiles never exposes a placeholder to the read/search tools. Adds a
regression test (empty → still visible → file returns → picked up).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: trim read_file char-cap output to match its pagination note
When the char cap cut partway into the line after some whole lines, readFile set
the note/endLine to the last complete line but still returned the partial next
line in `text` — so read_file showed (line-numbered) a line the note said would
come on the next read. Trim the returned text back to the last complete newline
so the body and the note agree. Test now asserts res.text for that case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: isolate search_files in a Worker (ReDoS) + path-aware folder dedup
- search_files runs a model-supplied regex, and a catastrophic-backtracking
pattern (e.g. /^(a+)+$/) can't be interrupted mid-test, freezing the tab. Run
the search in a Web Worker (searchFilesInWorker) and terminate it on a timeout,
returning "pattern too expensive" instead of hanging. Degrades gracefully to a
main-thread search where Workers are unavailable / fail to load.
- #isDuplicate keyed its content check on the file basename, so two distinct
files sharing a basename under different folder subdirs (proj/a/index.ts vs
proj/b/index.ts) were wrongly deduped and silently dropped from snapshotted
folders. Key it on the relative path instead.
Adds tests: worker result/timeout-and-terminate, and same-basename-different-subdir.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: keep an initially-empty live folder linked (placeholder + persist)
addFolder only created rows / persisted the dir-handle when at least one text file
was found, so linking a folder that's empty (or all-binary) at pick time was a
silent no-op: no chip, nothing persisted, and refreshFolders had no source to
re-enumerate when files were added later. Now it always leaves a placeholder
(#ensureFolderRow) and persists the handle — matching the became-empty behavior —
so the folder stays visible, survives reload, and picks up files added afterward.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: keep empty-folder placeholders out of the real-file name space
The placeholder row for an empty live folder uses name = folder, which could
collide with a standalone file of the same name: addFiles deduped the file
against the placeholder, removeFile(name) dropped both rows, and #uniqueName
pushed the file to a "(2)" suffix. Placeholders are managed via removeFolder and
never read by the tools, so exclude isFolderRoot rows from #isDuplicate,
removeFile, get(), and #uniqueName. Adds a placeholder/standalone collision test.
(codex's other nit — @-mentions not highlighting filenames with spaces — left as
a known cosmetic limitation per the chosen scope.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: highlight @-mentions of filenames containing spaces
A file mention was inserted verbatim as `@my file.txt`, but the highlighter regex
`@[\w/.\-\[\]]+` stops at the space, so only `@my` was parsed/highlighted and the
mention didn't behave as advertised. Introduce a small shared `mention` module:
names with whitespace are inserted in a bracketed form `@[my file.txt]`, and the
shared regex + `mentionTitle` parse both bare and bracketed tokens. Both insertion
entry points (the inline `@` picker in ContextTextarea and the toolbar path in
AIChatInput) now use `formatMention`, so the full name highlights.
Verified in a real browser: `@[my file.txt]` renders as a single highlight span.
Unit tests cover format/parse/round-trip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: search_files reports a requested file's real status, not "not attached"
search_files filtered the store down to readyFiles() before validating a
requested `file`, so searching an attached-but-not-ready file (indexing / errored
/ locked / unavailable) while another file was ready returned "No attached file
named X" — even though it is attached. Factor read_file's status reporting into a
shared notReadyMessage() and have search_files report the same accurate status
before searching the ready subset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: clear attached files on new/loaded chat in the non-session global chat
saveAndClear() (the "New chat" button) and loadPastChat() left attachedFiles
intact. In an AI session that's intended — files are session-scoped and persist
across conversations. But the ephemeral global side-panel chat has no session, so
the next, unrelated conversation still got the previous file roster injected and
could read_file/search_files against it. Clear attachments on both transitions
when `!isSessionChat`; sessions keep them. Adds a lifecycle regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: keep an empty folder linked when regranting access after reload
regrantLocked() dropped the locked placeholder unconditionally after #expandFolder.
If the regranted folder was empty (or all-binary), #expandFolder's #ensureFolderRow
no-op'd (the locked placeholder still existed), so dropping it removed the only
handle-bearing row — unlinking the folder and stopping future refreshFolders from
ever seeing files added back. Re-ensure a ready placeholder after dropping the
locked one. Adds a regression test for the empty-regrant path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: round-trip @-mentions of filenames containing a closing bracket
The bracketed mention form `@[name]` broke when the name contained a `]`
(e.g. `notes ] draft.md`): the regex stopped at the first `]` and mentionTitle
resolved the wrong name, so it wouldn't highlight. Escape `\` and `]` when
bracketing, match escaped chars in MENTION_RE, and unescape in mentionTitle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: highlight @-mentions of filenames with HTML-sensitive / special chars
getHighlightedText() escapes the textarea value to HTML before parsing mentions,
then looked the parsed title up against raw attached names — so a file like
`R&D notes.md` (escaped to `R&D notes.md`) never matched and wasn't highlighted.
Also, names with chars outside the bare set (`<`, `>`, `&`, parens, …) weren't
bracketed, so the bare regex truncated them. Now formatMention brackets any
non-bare-safe name, and the highlighter HTML-unescapes the parsed title before the
store lookup. Verified in a real browser with `R&D notes.md`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: report the real reason search_files has no readable targets
When attachments existed but readyFiles() was empty, search_files always told the
model "still being indexed, try again shortly". That's wrong for the placeholder
states this PR introduces: an empty or binary-only linked folder leaves only a
filtered-out `ready` placeholder, and a locked/unavailable restored folder exposes
no readable children. Now the message reflects the actual state — no searchable
text, restore access, or re-link — and only says "indexing" when something is.
Adds a focused fileTools test for the empty-ready states.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(frontend): hide primary storage row until added in workspace storage settings
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): red border on empty storage resource picker
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): allow deleting primary storage when no secondary storages exist
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): disable storage save when a row is missing its resource
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(jobs): auto-grant approvers a run-detail view link on the approval page
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(approval): show a clear run-details button for logged-in workspace members
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(approval): build run-details link from route params, not undefined job
getJob is fire-and-forget on the new approval page and is denied for an approver who lacks direct run read access — the exact case this link serves — leaving job undefined and producing /run/undefined. page.params.job is the flow id the view_token is minted for.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The debounce assertion resolved the dispatched job's debounce window through
`prefetch_cached_from_handle`, which goes through the process-global
runnable-settings cache (shared by every test running concurrently in the
binary) and its tempdir-backed file I/O. Read the persisted rows directly from
the test's isolated DB instead, removing that cross-test coupling and extra I/O
from the assertion path. Still validates the full wiring
(handle -> runnable_settings -> debouncing_settings).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(home): preserve URL filter state on page refresh
ListFilters.loadFilterFromUrl() runs synchronously at script init and
sets ownerFilter via binding. When $workspaceStore resolves asynchronously
after mount it triggered the $effect that resets ownerFilter, wiping the
URL-loaded filter before the user saw any results.
Skip the first $workspaceStore resolution using the same firstRun guard
pattern already used in this file (firstWorkspaceRun). Workspace switches
still correctly clear the filter.
Fixes#9624
* refactor(home): reset filters on workspace change instead of first-run guard
Track the previous workspace value and clear filters only when it actually
changes, rather than skipping the first $workspaceStore resolution. Encodes
the real invariant (reset on change) without depending on child/parent init
ordering, and preserves URL-loaded filters on initial mount by construction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: validate websocket trigger urls and gate trigger test route
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: clarify validate_websocket_url_for_ssrf call sites
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): refetch pipeline dispatch edges on live runs so cascades group
The activity panel groups runs into cascades by connected components of the
dispatch-edge graph, but edges came only from the one-shot history preload
(refetched on workspace/folder/days change). dispatch_event rows are written
server-side when a producer completes, so a run launched live had its producer
and freshly-dispatched children appear as live poll events with no connecting
edge — they rendered as separate ungrouped rows instead of one cascade.
Add an edges-only refetch and trigger it whenever the live poll's event id-set
changes, so live cascades converge to grouped like historic/scheduled ones.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): address review nits on live dispatch-edge refetch
- Sequence same-scope edges-only refetches with a monotonic edgeSeq so a
slower earlier response can't overwrite a newer one mid-cascade (the gen
counter only guards scope changes).
- Condense the duplicated edge-refetch rationale: keep the canonical "why"
in loadEdges, trim the page effect comment to its trigger/loop invariant.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(backend): only emit asset producer-change event on write-set changes
Data Pipelines (#9193) made every script deploy emit a
`notify_asset_producer_change` event: `clear_static_asset_usage` inserted
into `notify_event` unconditionally on every clear, and the per-asset
insert path emitted nothing. So a plain script with no assets — the
overwhelming majority of deploys — wrote a `notify_event` row that made
every worker drop its `ASSET_PRODUCER_WRITES_CACHE` entry instance-wide,
needlessly thrashing the cache the feature added.
That cache only tracks script rows with write access (`usage_access_type
IN ('w','rw')`), so a deploy changes it only when the script gains or
loses a write producer. Gate the event on exactly that:
- `clear_static_asset_usage` / `clear_static_asset_usage_by_script_hash`
emit only when the delete removed a 'w'/'rw' row (via `RETURNING`).
- `insert_static_asset_usage` emits only when it actually inserts a
'w'/'rw' script row (no-op `ON CONFLICT`, read-only, and flow usage
stay silent).
Plain, read-only, and flow deploys now emit nothing; producer-changing
deploys still invalidate, atomically and visible-only-on-commit as
before. Adds a test asserting the emit/no-emit matrix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(backend): dedup producer-change notify on write-asset redeploys
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(backend): derive replace write-set from persisted rows; document auth contract
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(backend): correct replace_static_asset_usage call-site comment
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: add global AI chat context-optimization plan for raw apps
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(ai-evals): add global raw-app debugging cases on a large fixture
Adds a ~20-file analytics_dashboard raw-app fixture (incl. a 5k-line data module
and a planted wrong-totals bug), two global cases (read-heavy debug + small-edit
baseline), app-seed support in the mock backend, directory-fixture loading, and a
decorateHelpers seam so read-dedupe is measurable. Records tokenUsage for before/
after comparison of the read-tool optimization.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-chat): cap and dedupe read_app_file to bound context in large apps
read_app_file now defaults to a head slice (1500 lines / 50k chars) with offset/
limit to page further, and skips resending a file whose earlier read is still in
context (per-conversation ledger keyed off the originating tool-call id, so it
self-heals after compaction). Bounds the file-content portion of global-chat
context when working in large raw apps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(ai-evals): add read-heavy raw-app debug case (large data module)
global-test31 induces the model to inspect the 5k-line seedData module, exercising
the read_app_file cap/offset path. Baseline ~262k tokens vs ~200k with the cap+dedupe
change (-24%).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: record A+B benchmark results and fixed-overhead finding
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-chat): clearer read_app_file past-EOF message + unit tests for cap/dedupe
Addresses local-review nits: out-of-range offset now reports 'offset N is past the
end of the file' instead of a backwards 'lines 11-10' label; adds unit coverage for
the slicing (line cap, offset/limit window, char budget, past-EOF) and re-read dedupe
(hit + miss-when-not-retained).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-chat): char-level paging + per-range dedupe for read_app_file
Adds char_offset/char_limit so minified/long-line files can be paged within a line
window, keys the re-read ledger by range (so reading different ranges no longer
collides), and dedupes on the full-file hash (a cached range stub is invalidated
when any byte of the file changes, not just the returned range). Tests updated for
the char-slice behavior plus single-line capping, char paging, and out-of-window
change detection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(ai-chat): add read_app_file context micro-benchmark + re-read eval case
Adds a deterministic micro-benchmark (no LLM) that drives read_app_file through a
realistic big-project read pattern (large file, re-read, minified bundle, paging)
and asserts the cap+dedupe cut returned context >50% vs the old whole-file behavior
— isolating the feature's effect from model nondeterminism and guarding against
silent weakening. Adds global-test32, a cross-file consistency investigation that
revisits overlapping files so re-read dedupe is exercised in a real run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(ai-evals): clarify test32 measures the read cap, not dedupe
Verified: sonnet and haiku both read each file once per conversation and retain
it, so test32 never triggers read_app_file re-read dedupe. Dedupe is measured
deterministically by the micro-benchmark instead. Comment corrected to match.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai-chat): drop read_app_file re-read dedupe, ship the cap only
Benchmarking showed the per-conversation re-read dedupe never fires in practice:
across sonnet/opus/gpt-5.5/haiku, every model reads each file once per conversation
and keeps it in context (0 within-conversation re-reads). It was a correct but unused
guard, so this removes the ledger, full-file hash, retention predicate, the
AIChatManager wiring, and the eval decorateHelpers seam — keeping the read cap +
offset/limit/char paging (A), which is the lever that actually bounds context. The
micro-benchmark is now cap-only; test32 is kept as a multi-file read-load case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-chat): add search_app grep tool for global raw-app chat (experimental)
Client-side grep over a raw app's frontend files and inline runnables (literal,
case-insensitive, optional file_glob/context_lines/max_matches, head-capped).
Completes the list -> search -> ranged-read triad. Includes the eval A/B gate
(WMILL_AI_EVAL_DISABLE_SEARCH_APP), unit tests + micro-benchmark, and a
find-all-usages eval case (global-test33).
Experimental: A/B benchmarking shows it is not an unconditional win — it helps
on find-all-usages but adds agentic iterations on navigable apps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(ai-evals): accept search_app as a valid file-inspection tool in raw-app cases
Add requiredToolsAnyOf alternatives-group to ToolValidationSpec and switch
global-test29..32 to it so a model that locates files via search_app instead
of read_app_file no longer false-fails the tool assertion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: remove stale ai-chat context-optimization planning doc
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai-chat): drop read_app_file char paging for a hard char cap
The char_offset/char_limit params guarded minified files (a single line over
the char budget) but were effectively unused in benchmarks. Remove them and the
in-window char paging; keep the hard 50k-char budget and, when a read hits it,
tell the model to narrow the line limit (or treat the file as unreadable if a
single line exceeds the budget). Proper long-line handling is left as a TODO.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai-chat): bake search_app context to 1 line, clarify query is literal
Drop the context_lines param (models varied it to little effect) for a fixed
SEARCH_APP_CONTEXT_LINES=1, and cap on matching lines instead of pushed rows so
max_matches stays accurate with context always on. Sharpen the query description
to state it is a literal (non-regex) substring and to suggest the call form
(e.g. formatCurrency() to hit call sites and skip formatCurrencyPrecise.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai-chat): widen baked search_app context to 2 lines
Models that set the old context_lines param leaned to 2; match the lean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-chat): count every file with a match in search_app header
Move fileHadMatch ahead of the render cap so files whose matches fall past max_matches are still counted (with a regression test). Also swap the raw NUL globstar sentinel for a printable escape (the NUL bytes made core.ts read as binary to grep) and reword two comments to describe current constraints instead of drafting history.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-chat): drop redundant input echoes from app tool results
read_app_file and search_app no longer prefix results with the tool name or echo back the caller's own inputs (file path, query, file_glob) — the model already has them from the call args, and the unbounded query echo could push the search result past its output budget. Keeps the useful signals (line range, match/file counts, truncation) and the actionable advice. Also reword max_matches to 'matching lines' since it caps lines (each expands to context rows). Unit tests updated to the new format.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
draft.value is a json column (not jsonb), so a client could store a U+0000
escape in it. Any later text extraction (`->>` / `to_jsonb`) on such a value
raises 22P05 "unsupported Unicode escape sequence" — one poisoned draft 500'd
the whole GET /drafts/list, silently hiding the home-page "This workspace has N
drafts" banner (and breaking the global drafts page).
Prevent it at the source: sanitize the value in update_draft (the only path that
writes client-supplied draft content) so a NUL never reaches the column.
strip_json_nul does a single backslash-parity-aware byte pass that removes real
NUL escapes (values and keys alike) while leaving a legitimate escaped backslash
intact — O(n) with no serde_json::Value tree to allocate, important because the
slow path is also hit by any value legitimately containing the text after a
backslash (e.g. script source). The clean path is a single substring check.
A SQL migration scrubs rows written before this, gated to genuinely-poisoned
rows (a real NUL makes value::jsonb raise, distinguishing it from a legitimately
escaped backslash). With the data clean, no read-side query needs to change.
Tests: unit tests for the strip helper (escaped-backslash no-op, real+literal
collision, odd-backslash-run parity, nested keys/values) and an integration test
that POSTs a NUL-bearing draft and asserts it is stored and listed NUL-free
(fails without the strip).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(python): split PIP_TRUSTED_HOST by whitespace for multiple hosts
When PIP_TRUSTED_HOST contains multiple space-separated hostnames, the
whole string was passed as a single --trusted-host argument
(--trusted-host "host1 host2") rather than one flag per host. This
matches pip's documented PIP_TRUSTED_HOST convention by emitting a
separate --trusted-host for each host, mirroring the existing
pip_extra_index_url handling.
Fixes WIN-2077
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(python): use shell word-splitting for nsjail trusted-host args
Address review feedback: the previous tr/sed pipeline only split on
single spaces, diverging from the Rust split_whitespace() paths. Repeated
or leading/trailing spaces produced empty --trusted-host flags and
tab-separated hosts were not split. Use an unquoted for-loop over
$TRUSTED_HOST so the shell's own IFS word-splitting handles arbitrary
whitespace and skips empty fields, matching the non-nsjail paths.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The workspace_id column on unique_ext_jwt_token (migration 20260409145556)
has no FK constraint on the workspace table, and delete_workspace did not
remove its rows. Deleted workspaces left orphaned external JWT token records
that kept appearing in the superadmin External JWTs listing.
Add a DELETE FROM unique_ext_jwt_token WHERE workspace_id = $1 alongside the
other per-table cleanup statements in delete_workspace.
Fixes WIN-2078
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The script_trigger table (migration 20260423050000_script_trigger)
relied on ALTER DEFAULT PRIVILEGES to grant access to windmill_user and
windmill_admin. Those default privileges only apply to objects created
by the role that set them (migration 20250205131523), so deployments
whose migration runner is a different role leave script_trigger
ungranted.
Direct application writes run as the invoking role and fail with
"permission denied for table script_trigger" — notably
clear_script_triggers and insert_script_trigger in
windmill-common/src/assets.rs during every script save.
Add an explicit GRANT on script_trigger and its sequence, matching the
notify_event fix (#9665) and the asset table precedent.
Fixes WIN-2076
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): ignore hash/assets in script diffs and drafts
The script editor's draft value is seeded from the full `getScriptByPath`
DB row (since #9351), so it carries `hash` (the deployed version's
identity) and `assets` (re-derived from the script content by the
editor). Neither is editable draft content, yet both were persisted into
the draft row and surfaced as spurious changes in the workspace/fork
compare diff view.
- Add `hash`, `assets`, and the read-time-computed `inherited_labels` to
`CLEANED_VALUE_KEYS` so the shared diff/unsaved-change strip ignores
them everywhere (DiffDrawer + WorkspaceItemDiffViewer).
- Strip `hash` and `assets` from script drafts at the single persistence
chokepoint (`UserDraftDbSyncer.save`) so every path — reactive
autosave, Ctrl/Cmd+S flush, the pagehide keepalive — sends the same
trimmed payload. On reload the deployed row re-supplies them.
Fixes WIN-2071
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(frontend): clarify draft sanitizer vs diff-strip relationship
The two field lists are intentionally not equal — only the hash/assets
overlap must stay consistent. Reword the comment so a future maintainer
doesn't add keys to one expecting parity with the other.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ansible scripts previously lacked the AI assistant and the contextual
variable helper that other scripting languages expose in the script editor.
- Add 'ansible' to SUPPORTED_CHAT_SCRIPT_LANGUAGES so the AI chat button
shows in the editor toolbar and the AI chat opens in SCRIPT mode without
the "language not supported" warning.
- Add an Ansible system prompt (system_prompts/languages/ansible.md) plus a
LANGUAGE_METADATA entry, and regenerate the auto-generated prompts/skills
so the AI has tailored Ansible context.
- Show the contextual variable picker for ansible and insert references as
`{{ lookup('env', 'NAME') }}`, matching how Windmill exposes reserved
variables as environment variables to the ansible-playbook process.
Fixes WIN-2072
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop empty/unknown `type` values (Windmill emits `type: ""` for untyped fields) and infer `type: array` for nodes carrying `items`, so generated MCP tool schemas validate against JSON Schema draft 2020-12. Anthropic's tool registration rejected the whole tool list otherwise.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: trigger flow error handler on unrecoverable (OOM/zombie) step failures
When a worker is OOM-killed mid-step, the zombie job handler fails the step
via handle_job_error with unrecoverable=true. update_flow_status_after_job_completion
had `false if unrecoverable => false`, which silently completed the flow with the
error and skipped the flow's failure module (error handler). It would also have
pinned the failure module to the dead worker via same_worker.
Unrecoverable failures now route to the failure module instead of being retried or
silently dropped, and the error-handler step is pushed as a regular queued job that
any live worker can pick up.
Fixes WIN-2070
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: skip retry on unrecoverable flow failures, add retry-skip regression test
Address review: the failure-module-on-unrecoverable change must also bypass the per-step retry policy in push_next_flow_job, otherwise an OOM/zombie-killed step with a retry config would be retried instead of routing to the error handler. Gate the retry evaluation on !unrecoverable and add a regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: add sqlx offline cache for new flow-step zombie test query
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: route unrecoverable continue_on_error step failures to the error handler
Addresses Codex/Pi review (P1): with continue_on_error on the failed step, the
step counter was advanced before the unrecoverable decision branch, so
push_next_flow_job pushed the next normal step instead of the failure module —
hiding the worker death and letting the flow complete successfully.
- Do not advance the step counter (inc) for an unrecoverable continue_on_error failure.
- Let the Failure arm in push_next_flow_job route to the failure step even on a
continue_on_error module when unrecoverable.
- Add a regression test (a[continue_on_error] -> b + failure_module): asserts the
failure module runs and step b does not.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(backend): auto-reconnect postgres trigger listener with backoff
The Postgres trigger listener permanently disabled itself on any
connection error (stream close, receive error), so a transient network
interruption (e.g. a cloud provider maintenance window) permanently
killed the trigger.
Restructure the listener to match the Kafka trigger: the replication
connection is now established inside an outer reconnect loop in
`consume`. On a dropped stream or receive error it backs off 30s and
reconnects instead of disabling, reporting a critical error every 10
failed attempts and a recovery once it reconnects. Disabling is kept
only for unrecoverable misconfiguration (missing publication or
replication slot, unparsable replication message).
Fixes WIN-2073
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(backend): count postgres reconnects on stream drop and alert from first
Adopt the SQS listener's reconnection accounting in the Postgres trigger
listener. A stream close or receive error now counts toward the retry
counter and raises a throttled critical error (on the first occurrence,
then every 10 attempts), and the retry counter is reset / recovery is
reported only once the reconnected stream actually delivers a message.
Previously the inner-loop disconnect branches reset `tries` to 0 on every
successful (re)connect and never alerted, so a stream that connected and
then immediately dropped could ping-pong every 30s indefinitely without
ever raising an alert. Resetting on real progress rather than on a bare
connect closes that blind spot and matches the SQS pattern.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The notify_event table (migration 20260203172950_polling_based_events)
relied on ALTER DEFAULT PRIVILEGES to grant access to windmill_user and
windmill_admin. Those default privileges only apply to objects created
by the role that set them (migration 20250205131523), so deployments
whose migration runner is a different role leave notify_event ungranted.
Trigger inserts were already worked around with SECURITY DEFINER
(migration 20260206060555), but direct application inserts that run as
the invoking role still failed with "permission denied for table
notify_event" — notably clear_static_asset_usage in assets.rs during
script save, and restart_worker_group in settings.
Add an explicit GRANT on notify_event and its sequence, matching the
existing explicit-grant pattern used for the asset table.
Fixes WIN-2074
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The raw-app editor keyed its autosave handle on a non-reactive `path`
`let`. SvelteKit does not remount the page on same-route navigation, so
the post-deploy `goto` (draft_{uuid} → chosen path) left the handle stuck
on the old draft slot. Edits to the just-deployed app then autosaved to a
dead key, so autosave appeared broken. Key on the reactive
`page.params.path` instead, matching /scripts/edit.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The home list's draft user badges come from list_apps' `draft_users`
subquery, which only matched `draft.typ = 'app'`. `app` and `raw_app`
are separate draft kinds over the one `app` table, so a deployed raw app
with a pending draft had `is_draft = true` (the join already matches both
kinds) but an empty `draft_users` — the row showed a "Draft" badge with
no owner badge. Match `typ IN ('app', 'raw_app')`, consistent with the
`is_draft` join and the draft-only query.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
finishAppDraftWrite returned `item: result.item`, whose `value` is the entire
app draft (every frontend file body and inline runnable). Each write_app_file /
patch_app_file / write_app_runnable therefore re-sent the whole app back to the
model; on a large app a few edits overflow the 200k context window.
This restores #9530 (which removed the echo) — the DB-backed-draft refactor
(#9601) reintroduced it by routing all app writes through this shared helper
with `item:` re-added. Write results now return only `{ success, message }`,
matching the flow write tools. Adds a regression test asserting the value is
not echoed.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Record finalContextTokens per attempt: the input-token total of the last
model request (input + cache-creation + cache-read), i.e. how full the
context window ended up. Complements the cumulative tokenUsage.prompt,
which conflates context size with loop-iteration count.
Captured generically in the shared frontend runEval via the chat loop's
lastIterationUsage, so it covers all frontend modes (global/flow/script/
app), plus CLI mode via the last assistant turn's usage. Aggregated as
average and max over passed attempts and printed in the run summary.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add workspace asset graph view
Workspace-wide canvas of assets and their producer/consumer scripts,
reachable from the assets page. Left-to-right layered layout via
d3-dag sugiyama, rendered with @xyflow/svelte (same stack as the
flow editor). GET /w/:ws/assets/graph returns deduped nodes + edges.
Follow-ups: filters (kind/folder/search), node detail drawer, inline
script edit from a clicked node.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* all
* all
* all
* update
* all
* all
* all
* feat(pipeline): output-kind picker and per-(lang, output) templates
Add a third stage to PipelineInsertMenu that asks what kind of asset the
new script will produce (datatable / ducklake / s3 parquet / s3 object /
none). The picked kind drives a real wmill SDK skeleton — typed
datatable inserts, ducklake CREATE+INSERT, s3 parquet COPY, etc. — with
the upstream asset auto-wired as the input source when added from an
asset node. Reorder languages to bun → duckdb → python → sql so
data-shaped languages surface first.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* all
* chore(main): release 1.693.4 (#8994)
* chore(main): release 1.693.4
* Apply automatic changes
---------
Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
* feat: ansible delegate_to_git_repo install_requirements, dynamic fields, --limit (#8997)
* feat: ansible delegate_to_git_repo install_requirements, dynamic fields, --limit
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: include .yaml variants in collections/roles requirements lookup
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 (#9000)
* 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>
* feat(cli): wmill-lock.yaml auto-fill + --rehash-only + path-prefix dedup (#8978)
* 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: use otel.status_message for OTLP Status.message on failed jobs (#8995)
tracing-opentelemetry only recognizes otel.status_code and
otel.status_message as fields that map to the OTLP Status proto.
The previously-used otel.status_description fell through to the
generic attribute recorder, leaving Status.message unset and
preventing OTLP consumers from filtering spans on error status.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: route email trigger path through standard info channel (#8996)
* 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>
* update git sync version to 1.693.5
* fix: pair PG arg type with actual Rust binding to keep query_typed_raw safe (#8999)
* fix: pair PG arg type with actual Rust binding to keep query_typed_raw safe
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(pg): wrap encoder errors with arg context, add fallback test
Followups on #8999 review:
- Wrap rust-postgres "error serializing parameter N" failures with the arg
name, JSON value kind, and asserted Postgres type plus a hint about an
explicit cast — so users see actionable context instead of an opaque
WrongType.
- Drift-prevention meta-test: assert otyp_to_pg_type and convert_val agree
on the Type for every recognised arg_t when the JSON value matches its
natural Rust kind. Catches future drift if either side changes.
- Integration test for the prepare + query_raw fallback path: confirms
unrecognised arg_t (custom enum) is routed through prepare and the
server-resolved type appears in the failure surface — flips into a
test failure if a regression accidentally routes unrecognised types
through query_typed_raw.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): add otyp_inferred flag + regex-based placeholder renumbering
Two follow-ups from the review of #8999:
1. **Issue #1 (Number/Bool + explicit text decl in WHERE)**
Add `Arg::otyp_inferred: bool` to the parser. The PG SQL parser sets
it `true` only at the "no info → fall back to text" site (bare `$N`,
no inline cast, no `-- $N (TYPE)` decl). All other arg sources keep
it `false`.
In `convert_val` this flag distinguishes:
- explicit text-like target (`-- $1 (text)` or `$1::text`) — coerce
`Bool`/`Number` → `Box<String>` so `WHERE text_col = $1` works
(`text = text` operator). Pre-#8988 behaviour, restored.
- parser-default text (bare `$N`) — bind the value's natural Rust
type so the regression case (`Value::Bool` against a real `bool`
column via `CAST AS bool`) keeps working.
`Arg` is in `windmill-parser`; the new field has `#[serde(default)]`
so persisted signatures stay backward-compatible.
2. **Issue #4 ($5/$50 substring rewrite collision)**
Replace the per-index `String::replace` chain (which turned `$50`
into `$10` when oidx=5 was processed first) with a single regex
pass. `\d+` is greedy, so `$5` and `$50` match as distinct units;
indices outside the mapping are left intact.
3. Tests:
- parser: `test_parse_pgsql_otyp_inferred_flag` covers bare/inline-
cast/decl/mixed shapes.
- executor unit: `convert_val_bool_against_every_arg_t` and
`convert_val_*_number_*` split each text-like target into explicit
vs inferred expectations.
- executor unit: `renumber_sparse_placeholders_no_collision`.
- integration: `test_postgresql_arg_type_combinations` adds 4 cases
covering decl(text)+Number/Bool in WHERE, bare $1+Bool, and
sparse positional args ($5/$50).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg+sdk): enum support, extended String arms, position-aware $N rewrite, SDK quality
Backend:
1. **`AnyTextValue` ToSql/FromSql wrapper**: vanilla `tokio_postgres`'s
`ToSql for String` / `FromSql for String` reject `Kind::Enum` and
`Kind::Domain` even though the wire format is plain UTF-8. The wrapper
accepts those kinds in both directions. End result: explicit
`$1::my_enum` / `CAST($1 AS my_enum)` casts now round-trip without the
ugly `CAST($1::text AS my_enum)` workaround, AND `SELECT enum_col`
results come back as JSON strings instead of erroring at the FromSql
layer.
2. **#10 — Value::String → numeric/real/double/oid/bool**. Without these
arms, a string-encoded value (`"3.14"`, `"true"`) for a non-text /
non-temporal arg_t fell through to `Box<String> + TEXT`, which then
failed at the server (no implicit cast text→numeric in expression
context). Now strings are parsed into the matching native type with
clear error messages on parse failure.
3. **Position-aware `$N` rewrite**: replaces the regex-based renumbering
(which fixed the `$5/$50` substring collision but still walked through
string literals and comments, mangling `'price: $5'` etc.) with a
walk over `parse_pg_statement_arg_positions` — the same
string/comment/dollar-quote-aware tokenizer used for index discovery.
Adds `parse_pg_statement_arg_positions` to the parser's public API.
SDK:
4. **BigInt support**: `JSON.stringify(BigInt)` throws. The SDK now
stringifies bigints before serialisation; the executor accepts
numeric strings into BIGINT arg slots via the existing
`Value::String → INT8` parsing arm. SDK-side `inferSqlType` is split
so `BigInt` always resolves to `BIGINT` (was reaching
`Number.isInteger(BigInt)` which returns false → wrong default).
5. **Homogeneous array auto-tag**: `${[1,2,3]}` against an `int[]` column
now emits `$1::BIGINT[]` instead of `$1::JSON`. Detection covers
primitive types only (number / bigint / string / boolean); mixed or
nested arrays still fall back to JSON. Mixed int/float widens to
`DOUBLE PRECISION[]`.
6. **`.query()` positional bug**: previously the `.query()` method
abused the template-tag builder, which appended `$N::TYPE` after the
user's literal SQL string instead of binding by position
(`SELECT $1, $2` became `SELECT $1, $2$1::BIGINT`). Now `.query()`
builds the executor-shaped content directly: a `-- $N argN (TYPE)`
declaration block followed by the user's SQL verbatim.
Tests:
- Parser: `test_parse_pg_statement_arg_positions_skips_strings_and_comments`
asserts string literals, comments, and dollar-quoted blocks don't
produce positions (so renumbering doesn't mangle them).
- Executor unit: `renumber_sparse_placeholders_no_collision_no_string_mangling`
uses the new position-aware path and includes string-literal + comment
+ `$$…$$` cases. Existing convert_val tests grow to cover new
String→numeric/real/double/oid/bool arms.
- Integration: `test_postgresql_arg_type_combinations` adds 13 cases
(enum round-trip both directions, string→numeric/real/double/bool/oid,
string-literal `$N` non-mangling). The prepare-fallback test now
asserts SUCCESS (not failure) for enum encoding via AnyTextValue.
- SDK: new `typescript-client/tests/sqlUtils.test.ts` (42 tests)
exhaustively covering inferSqlType primitives + arrays,
parseTypeAnnotation, datatable() template tag (with all the new
shapes — BigInt, homogeneous arrays, RawSql, schema preamble),
datatable().query() positional, and ducklake() shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): replace DISCARD ALL with curated reset (preserves typeinfo cache)
Found while exhaustively probing custom-type DX: every cached-connection
reuse was running `DISCARD ALL`, whose included `DEALLOCATE ALL`
deallocates *all* prepared statements server-side — including the typeinfo
statements that tokio_postgres caches per-Client to resolve custom enum /
domain Oids. tokio_postgres still held `Statement` objects whose names
the server had forgotten, so the next custom-type query failed with
intermittent "prepared statement \"sN\" does not exist" errors. The
failure was easy to reproduce: any sequence that forced typeinfo lookup
for two different custom-type kinds on the same cached connection (e.g.
enum followed by domain) would hit it.
Replace `DISCARD ALL` with a curated reset that explicitly targets the
state we actually care about, *without* touching prepared statements:
RESET ALL — GUC parameters (search_path, application
_name, statement_timeout, …)
RESET SESSION AUTHORIZATION — undoes both `SET SESSION AUTHORIZATION`
and `SET ROLE` (RESET ALL does NOT —
these aren't GUC parameters, so without
this an elevated role from a previous
job would silently leak)
UNLISTEN * — drops LISTEN registrations
CLOSE ALL — closes open cursors
Trade-off: temp tables, advisory locks (session-scoped), and user-created
PREPARE statements may persist across cached-connection reuse — rare in
datatable / PG-script workloads. tokio_postgres's typeinfo cache survives
intact, so custom enum / domain queries are fast on subsequent reuse.
Tests:
- `test_postgresql_custom_types_on_cached_connection` — runs 10×
alternating enum + domain queries on a cached connection. Pre-fix this
failed with `prepared statement "sN" does not exist` after the first
reuse; post-fix passes.
- `test_postgresql_set_role_does_not_leak_across_cached_connection` —
switches `SET ROLE` and `SET SESSION AUTHORIZATION` to a non-postgres
role, then runs a follow-up job and asserts current_user/session_user
are restored. Specifically catches the case where someone might switch
back to `RESET ALL` alone (which doesn't cover SET ROLE / SESSION
AUTHORIZATION) and silently introduce a permission-leak vector.
- All existing session-isolation tests
(`test_postgresql_cached_connection_resets_session`,
`test_postgresql_single_worker_session_isolation`,
`test_postgresql_100_jobs_cached`) continue to pass.
Found via end-to-end probing of datatable / PG-script DX, not previously
covered: the existing isolation tests only did `SET ROLE postgres`, the
connecting user, so the leak was invisible.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): address PR #8999 review (cubic + claude)
cubic (P1, real bug):
- `convert_vec_val` for `timetz` array asserted `Type::TIMETZ_ARRAY`, but
chrono `NaiveTime` only encodes for TIME (same caveat as the scalar
arm). Switch to `Type::TIME_ARRAY`; rely on PG's implicit `time→timetz`
assignment cast at the column site. Add an explicit unit test.
claude (#1, silent failure → explicit error):
- `Bool` + explicit `(char)` / `(character)` decl previously silently
bound BOOL, hoping the server would cast at the use site — but PG has
no implicit `bool→char` and the resulting error
("operator does not exist: bool = char") was opaque. Now error at
bind time with an actionable hint to use `bool` decl or pass the
value as a "t"/"f" string.
claude (#2, asymmetry doc):
- Object/Array still coerce to text on `matches!(typ, Typ::Str(_))`
(covers both explicit AND inferred-default text), unlike Bool/Number
which key on `explicit_text_target`. The asymmetry is intentional
(no implicit `jsonb → text` cast in expression context vs PG having
implicit `bool/int → text` casts) — added a body comment so future
maintainers don't try to "align" them.
claude (#3, perf):
- `parse_pg_statement_arg_indices` and `parse_pg_statement_arg_positions`
walked the SQL tokenizer twice. Fold into a single pass that derives
the index set from the position list.
claude (#4, fmt drift):
- `cargo fmt` over the parser crates I touched with perl scripts in the
earlier commit (windmill-parser-{sql,bash,ts,go,php,java,csharp,nu,py,
rust,graphql,yaml,r}). Net cosmetic.
claude (#5, parseTypeAnnotation):
- One-line caveat in the SDK's `parseTypeAnnotation` that the returned
string is presence-only (e.g. `${x}::DOUBLE PRECISION` returns
`"DOUBLE"`, `CAST(${x} AS int)` returns `"int)"` — neither matches a
real PG type, but the only consumer just checks `!== undefined`).
While here — discovered + fixed independently while exhaustively probing
DX:
- **Replace `DISCARD ALL` with curated reset** (`RESET ALL; RESET
SESSION AUTHORIZATION; UNLISTEN *; CLOSE ALL;`). DISCARD's
`DEALLOCATE ALL` killed tokio_postgres' typeinfo cache, producing
intermittent `prepared statement "sN" does not exist` errors on
custom-type queries after cached-conn reuse. New regression tests:
`test_postgresql_custom_types_on_cached_connection` and
`test_postgresql_set_role_does_not_leak_across_cached_connection`
(the latter catches the case where someone might switch back to
`RESET ALL` alone and silently introduce a permission-leak vector —
RESET ALL doesn't cover SET ROLE / SET SESSION AUTHORIZATION).
- **ISO-8601 timestamp results** (`pg_cell_to_json_value`). Pre-fix
`TIMESTAMP` was rendered with a space separator ("2024-01-15 10:30:00")
and `TIMESTAMPTZ` with " UTC" suffix ("2024-01-15 10:30:00 UTC") —
neither parseable by `date-fns parseISO`, JavaScript `new Date()` is
lenient enough to handle them but several frontend `App*Input.svelte`
components use parseISO and fail silently. Switched to ISO-8601 with
`T` separator and `+00:00` offset; arg-parsing path still accepts the
legacy " UTC" suffix for back-compat.
Test coverage:
- 17/17 unit (`pg_executor::tests`)
- 9/9 integration (`backend/tests/worker.rs`, `test_postgresql_*`)
- 27/27 parser (`windmill-parser-sql`)
- 42/42 SDK (`typescript-client/tests/sqlUtils.test.ts`)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): bounded one-shot warning on numeric precision loss + ISO-8601 + NaN handling
Found while probing PG-script DX with millions of numeric cells:
1. **Numeric precision-loss warning**: `numeric` results are still serialised
as JSON Number (back-compat — switching to JSON String would silently
break user code doing arithmetic on results), but we now detect
`Decimal -> f64 -> Decimal` round-trip failure and emit a single
job-log warning recommending a `::text` cast in the SQL. Bounded by
`NUMERIC_PRECISION_CHECK_BUDGET = 256` cells per query (one atomic
load + one fetch_sub on the hot path; first lossy value
short-circuits to a single load thereafter). Worst-case overhead on
a 1M-cell numeric-heavy query: ~25µs of checks + 5ns × N atomic
loads (vs. ~100ms unbounded).
2. **ISO-8601 timestamps**: `pg_cell_to_json_value` previously returned
`"2024-01-15 10:30:00"` (TIMESTAMP) and `"2024-01-15 10:30:00 UTC"`
(TIMESTAMPTZ) — neither parseable by date-fns `parseISO`, which is
what the apps `App*Input.svelte` components use, so timestamp values
silently failed to round-trip into date pickers. Switch to ISO-8601
(`T` separator + `+00:00` offset) on the result side; arg-parser
continues to accept the legacy `" UTC"`-suffixed format for
back-compat.
3. **Float NaN / Infinity results**: `Number::from_f64` returns None for
NaN / ±Inf, which `pg_cell_to_json_value` was raising as
"invalid json-float" — failing the *entire* query if any cell held
one of these special values. Now serialise them as JSON strings
("NaN", "Infinity", "-Infinity") and let the rest of the row come
through. Arg-side: `s.parse::<f64>()` already accepts the same
strings.
Tests:
- `decimal_fits_f64_losslessly_predicate` — covers fits / doesn't-fit
cases for the precision-loss predicate.
- `precision_check_budget_caps_per_query_overhead` — locks in the
budget cap and the loss-flag short-circuit.
- All 9 PG integration tests + 17 unit tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): add pg_advisory_unlock_all to reset; warn on missing args; honor decl defaults
While probing PG-script DX further found three more frictions:
1. **Advisory lock leak** (cubic P2): switching from `DISCARD ALL` to
`RESET ALL; RESET SESSION AUTHORIZATION; UNLISTEN *; CLOSE ALL;`
meant session-scoped advisory locks (`pg_advisory_lock`) leaked
across cached-connection reuse. Add `SELECT pg_advisory_unlock_all()`
to the chain — `DISCARD ALL` covered this implicitly via
`DISCARD PLANS / DEALLOCATE / pg_advisory_unlock_all` and we lost it
in the switch.
2. **Missing-arg silent NULL**: an arg declared in the SQL (e.g.
`-- $1 amount (numeric)`) but not provided in the args object was
bound as NULL with no error / warning. Misspelling the key in the
args object silently produced a row of NULLs — a notorious DX
debugging trap. Now: collect the names of declared-but-missing
args during dispatch and emit a single one-shot warning to the job
logs at end-of-query naming each one. Bound NULL is preserved for
back-compat.
3. **Declaration defaults ignored**: `-- $1 a (int) = 5` carries
`arg.default = Some(Number(5))`, but the dispatch fell straight to
NULL when the arg was missing. Now: respect the default —
user-supplied value > declaration default > NULL. Also fixes the
warning logic above (only warn for args that *don't* have a default).
Tests: existing 19 unit + 9 integration pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): multi-word PG types with [] suffix lost the array-ness; array arms accept stringified values
Two more frictions found while probing SDK end-to-end against a real
datatable resource:
1. **Multi-word array types lose the [] suffix in the parser**.
`transform_types_with_spaces` recognises aliases for "double
precision", "character varying", "timestamp with time zone", etc.
but its return type was `&'a str` — only the bare alias, never with
a trailing `[]`. The `RE_CODE_PGSQL` regex's `\w+` captures stop at
the first space, so the regex's own `(?:\[\])?` array-suffix branch
sees only `"double"` (not `"double precision[]"`); the `[]` was
silently lost. Result: `$1::double precision[]` (which the SDK now
emits for homogeneous float arrays via the new auto-tag) routed
through `Value::Array → Type::JSONB` and the server failed with
"cannot cast type jsonb to double precision[]".
Fix: switch `transform_types_with_spaces` to return `Cow<'a, str>`
and re-check the trailing bytes after a multi-word match. If they
start with `[]`, return `format!("{alias}[]")` — Owned. Single-word
types and the no-match path keep returning Borrowed slices, so no
allocation in the hot path.
2. **Array arms in `convert_vec_val` rejected stringified values for
numeric / int* / bool / oid / real / double**. The scalar `convert_val`
already parses strings into the matching native type for these arg_ts,
but the array variant only accepted JSON-native counterparts. Sending
`["1.5", "2.5", "3.5"]` against `$1::numeric[]` (e.g. via `unnest` for
bulk loading, or `JSON.stringify(BigInt[])` round-trip) failed with
"Mixed types in array". Now the array arms mirror the scalar ones —
`as_<native>().or_else(|| as_str().and_then(parse))` — so both shapes
round-trip cleanly.
Tests: 19 unit + 9 integration pass; existing parser tests cover the
multi-word array forms (the regex-cap behaviour didn't break for
single-word types, and Cow plumbing is transparent to all callers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(parsers): add otyp_inferred field to Arg literals in tests + 3 missed src files
CI failures: the perl-driven sweep that added `otyp_inferred: false` to
every `Arg { ... }` literal when I introduced the field in the parser
schema covered `src/lib.rs` files but missed:
- parsers/windmill-parser-bash/src/lib.rs (mass-edited but a
later format pass un-applied a few sites)
- parsers/windmill-parser-go/src/lib.rs (same)
- parsers/windmill-parser-graphql/src/lib.rs (same)
- parsers/windmill-parser-nu/tests/tests.rs (test file — not
swept the first time)
- parsers/windmill-parser-ts/tests/tests.rs (test file — same)
Also tightened the regex to handle `oidx: None` without the trailing
comma (some test files had the field as the last initialiser line).
`cargo build --features <CI feature combo> --workspace --all-targets`
is clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sdk): Date → TIMESTAMPTZ; NaN / ±Infinity → string
Two more frictions found while running the actual SDK end-to-end against
a live datatable resource:
1. **JS `Date`** fell into the typeof "object" branch and was tagged
`::JSON`. It worked accidentally for `${date}::timestamptz` via PG's
`json → text → timestamptz` implicit cast chain, but `${date}` against
a `timestamptz` column without a user-supplied cast bound the value
as a JSON string and the comparison `timestamptz = json` failed. Now:
`inferSqlType` recognises `Date` and tags `::TIMESTAMPTZ`;
`serializeArgValue` emits `Date.toISOString()` so the executor's
`Value::String → TIMESTAMPTZ` arm parses it cleanly.
2. **JS `NaN` / `±Infinity`** silently became NULL. `JSON.stringify(NaN)`
returns `"null"` per the JS spec, so the value reached the executor as
JSON null — the SDK's `::DOUBLE PRECISION` tag then bound a NULL
double. Fix: detect non-finite numbers in `serializeArgValue` and
stringify them as `"NaN" / "Infinity" / "-Infinity"`. The executor's
`Value::String → FLOAT8` arm (`f64::from_str`) accepts these literals
directly, and the result-side already renders the values as JSON
strings (matching round-trip).
SDK unit tests grow from 42 → 44 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(pg): integration coverage for multi-word arrays + stringified array elements
Locks in the two array fixes from the previous commit
(`fix(pg): multi-word PG types with [] suffix lost the array-ness`)
with end-to-end cases in `test_postgresql_arg_type_combinations`:
- `double precision[]`, `character varying[]`, `timestamp without time
zone[]` — verifies the parser keeps the `[]` suffix after multi-word
alias resolution.
- `numeric[]` / `int[]` / `bool[]` from stringified primitives — verifies
the array arms of `convert_vec_val` apply the same string-coercion
the scalar arms do.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style: fix indentation drift on otyp_inferred lines
cargo fmt cleanup of leftover indentation where the perl-driven sweep
that introduced the otyp_inferred field landed at the wrong column.
No behaviour change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: support assigning a worker tag to app inline scripts (#9002)
* feat: support assigning a worker tag to app/raw-app inline scripts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: omit empty tag field from inline script raw_code payload
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* style: shrink tag popover width
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat(pipeline): 2-col picker, draft path edit, save-all + leave guard
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* all
* all
* update
* fix(cli): forward HEADERS env var on every backend fetch call (#9075)
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(cli): add --parallel flag to generate-metadata (#9074)
* feat(cli): add --parallel flag to generate-metadata
* fix(cli): validate --parallel input and harden flush ordering
* perf(flows): skip flow_env DB+transform work when no resolution is needed (#9078)
* fix(cli-tests): stabilize flow lock-gen race + Windows path (#9080)
* 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>
* perf(flows): cache resolved flow_env per flow execution (#9079)
* perf(flows): cache resolved flow_env per flow execution
* perf(flows): tighten flow_env cache cap to 1024 and clarify memory note
* perf(flows): don't cache transient flow_env resolution failures
* chore(main): release 1.698.0 (#9076)
* chore(main): release 1.698.0
* Apply automatic changes
---------
Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
* fix: reject root-rooted paths in ansible playbook validator on windows (#9081)
* fix(native-triggers): serialize Google channel renewal across replicas (#9060)
* fix(native-triggers): serialize Google channel renewal across replicas
`sync_all_triggers` runs every 5 minutes on every windmill-app replica
with no leader election. Multiple replicas were each rotating the
webhook token, creating a new Google watch channel, and racing the
trigger UPDATE — leaving the loser's new token (in `token`) and channel
(in Google) orphaned. Cloud was accumulating ~5 leaked tokens/week
without the silent best-effort `delete_token_by_hash` ever logging a
warning.
Wrap each per-trigger renewal in a transaction and acquire the row with
`SELECT … FOR UPDATE SKIP LOCKED`. Contending replicas skip the row
instead of duplicating the work. The lock spans `rotate_webhook_token`
→ Google API call → `update_native_trigger_service_config` and is only
released on commit. Re-checks `should_renew_channel` after acquiring
the lock so a replica that committed seconds earlier doesn't trigger a
duplicate renewal.
The pattern matches existing batch-cleanup paths in `monitor.rs`
(job-retention sweep) and other `FOR UPDATE SKIP LOCKED` call sites.
Also logs at `debug!` when `delete_token_by_hash` finds no matching row,
so future investigations can distinguish "deleted" from "not found"
without changing the `Ok(false)` contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fixup! fix(native-triggers): serialize Google channel renewal across replicas
* fixup! fix(native-triggers): serialize Google channel renewal across replicas
fixup! fix(native-triggers): serialize Google channel renewal across replicas
Address claude review:
- #5: per-skip log info -> debug (expected outcome under SKIP LOCKED)
- #2: warn moved out of delete_token_by_hash to the call site that knows the
expected state (try_renew_channel_locked); other callers are race-prone and
shouldn't warn
- #3: NULL service_config now warns (anomalous case)
- #4: post-Google-API DB-update + commit failures log distinctly so the
channel-orphan case is grep-able
Plus: add 14d expiry to Google webhook tokens via ServiceName::webhook_token_expiration,
mint fresh ephemeral-webhook-{service}-{rd5} labels at create + rotate so the
existing 'ephemeral-' filter excludes them from user-token email/critical-alert
paths (no filter changes in 3 places). Orphans now self-clean via the existing
expiry sweep in monitor.rs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fixup! fix(native-triggers): serialize Google channel renewal across replicas
fixup! fix(native-triggers): serialize Google channel renewal across replicas
Address second-round review:
- Claude #1 (P2): username_override_from_label now strips the 'ephemeral-'
prefix for ephemeral-webhook-* labels, so created_by stays
webhook-{service}-{rd5} instead of changing to label-ephemeral-webhook-...
(preserves audit/job-list filter compatibility)
- Codex (P2): updated renew_channel doc — labels are no longer copied; rotate
mints fresh ephemeral-webhook-google-{rd5} with 14d expiration
- Claude #3 (optional): test_rotate_webhook_token now asserts the rotated
Google token has an ephemeral-webhook-google-* label and a populated
expiration
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fixup! fix(native-triggers): serialize Google channel renewal across replicas
fixup! fix(native-triggers): serialize Google channel renewal across replicas
Reconsider the previous fixup: stripping the 'ephemeral-' prefix made
created_by no longer match token.label exactly, defeating the linking
purpose. Just allowlist 'ephemeral-webhook-' alongside the other
recognized webhook/email/ws prefixes — created_by becomes
ephemeral-webhook-google-XXXXX, matching token.label exactly. The
'ephemeral-' substring also informs operators that this is a
system-managed auto-expiring token vs a user-managed webhook trigger.
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): bump svelte version in `wmill app new` template (#9084)
* 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>
* perf(flows): gate flow_env resolve on expr text and share cache with handle_flow (#9085)
* feat: parse windmill_failure field to tag run as failure (#9073)
* feat: parse windmill_failure field in job result to tag run as failure
* feat: preserve top-level fields when windmill_failure tags a run as failure
* fix: address review findings on windmill_manual_failure
* refactor: rename windmill_manual_failure to wm_failure and add wm_* aliases
* fix: prefer injected ManualFailure error over sibling name/message in OTel
* fix: hide _ENTRYPOINT_OVERRIDE jobs from script/flow history panel (#9088)
* fix(flows): populate error handler input args from failure picker (#9087)
* fix(flows): populate error handler input args from failure picker
* style(flows): fix indentation in failure-step branch
* fix(python): verify wheel RECORD on cache pull/install, finalize piptar (#9090)
The Python per-package dependency cache could persist an incomplete wheel
extraction with `.valid.windmill` set, then propagate that broken artifact
to every worker through the object store. Customer hit this on
argon2-cffi==25.1.0 (missing argon2/_utils.py), and previously on
botocore/httpx (truncated tars). Symptom is a runtime ImportError that
looks like a missing dependency declaration rather than a Windmill bug.
Three changes that together stop the propagation:
1. After `pull_from_tar`, parse the wheel's `<dist-info>/RECORD` and
confirm every listed path exists on disk before writing
`.valid.windmill`. On failure, wipe the directory and fall through
to a fresh local install — the next install also self-heals the
broken object-store entry by pushing a fresh tar.
2. After `uv pip install` succeeds, run the same RECORD check before
queuing the piptar upload or writing `.valid.windmill`. A bad install
never becomes the source of a broken tar in the object store.
3. Finalize the tar (`drop(tar.into_inner()?)`) before reading its bytes
for upload, so we never push an unfinalized archive (no end-of-archive
marker) to the object store.
Verified with a 60-package end-to-end integration test (first-fill →
clear-local-cache → re-pull-from-objectstore → corrupt-objectstore-tar
→ detect-and-self-heal). All 27 packages on the live test pulled cleanly,
and the deliberately corrupted argon2-cffi tar was caught with the exact
expected log line ("wheel RECORD lists files missing on disk: argon2/_utils.py")
and replaced with a fresh tar.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(main): release 1.699.0 (#9082)
* chore(main): release 1.699.0
* Apply automatic changes
---------
Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
* feat(cli): auto-infer args for `wmill app push` (#9091)
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>
* all
* fix(pipeline): live-update graph for annotations and body assets
* fix(pipeline): persist draft body edits across node switches
* fix(pipeline): persist live writes per draft to keep output node fresh after switch
* feat(pipeline): animate graph edges only while a runnable is executing
* feat(pipeline): add run button on script nodes + recomputing hint on preview
* feat(pipeline): compact preview layout, two-way Test/Run sync
* fix(pipeline): test button cross-browser placement (no overflow trick)
* style(log-viewer): replace took/mem-peak labels with timer/cpu icons
* style(log-viewer): hyphenate Auto-scroll label and prevent wrapping
* style(log-viewer): lowercase auto-scroll label, force vertical scrollbar
* style(log-viewer): force horizontal scrollbar instead of vertical
* fix(log-viewer): scope overflow-x to top bar so pre doesn't drive panel width
* fix(pipeline): overlay live body-asset writes for persisted scripts too
* fix(pipeline): persist inferred body assets at save so edges survive page reload
* fix(pipeline): snapshot live draft writes at persist time so they survive reload
* fix(pipeline): keep inferred body writes on the canvas across selection changes
* fix(pipeline): untrack inferredWrites cache mutation to break effect loop
* fix(pipeline): refetch asset graph after persisted-script save
* feat(pipeline): optional AI prompt when creating a pipeline script
* all
* all
* test: cover asset-trigger dispatch end-to-end through worker
* feat(pipeline): split-button Test with optional downstream cascade
* feat(pipeline): cascade option on graph Run + match button heights
* style(pipeline): match caret bg/text to Test button's accent-secondary
* feat(pipeline): split Run pill on graph node exposes cascade option
* feat: live run activity + status badges in pipeline asset graph
- folder-scoped queue poll lights up the downstream asset-trigger
cascade (not just the launched script); zero requests at rest,
catch-up for fast hops, auto-disarm when idle
- per-runnable node badge: last-run status + session run count
- animate unsaved/live-parsed edges (was unconditionally suppressed)
- background-pane click no longer clears selection
- run-bridge guarded so node selection/save no longer triggers a test
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: live activity log, optimistic badges, node-avoiding graph edges
- collapsible folder activity log (PipelineEventLog): live job feed,
polls only while open/active, slow idle cadence, capped + pruned
- composable: observe mode + events list + run-count anchored to
graph-open time (pre-existing history excluded)
- optimistic node badge: launched script shows running instantly via
the zero-latency activeRunnable hint, keeps the polled run count
- activity pane height capped (min(18rem,40vh)) then scrolls
- route asset-graph edges through sugiyama-computed waypoints so they
go around nodes instead of under them; bezier fallback for
adjacent-layer / draft-overlay edges
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: prefetch all folder script assets so graph is stable on load
On pipeline load, eagerly infer body assets for every persisted folder
script and seed the existing inferredWritesByPath overlay, instead of
only filling it when a node is selected. Scripts whose persisted asset
rows are missing (e.g. object-form writeS3File) now have their edges
from first paint, so clicking a node no longer re-layouts the graph.
One-shot per (workspace, base-graph) load, untracked map reads,
generation-cancelled, pool-capped fetches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* perf: guard no-op poll re-layout; dedupe write-asset extraction
- skip reactive ids/states/events reassignment when unchanged, so an
idle poll tick no longer re-runs the full sugiyama layout every 3-6s
- bound countedJobIds (rebuilt from eventsById in lockstep with prune)
- extract shared extractWrites() helper, replacing 4 copy-pasted
write-asset filter/map blocks in the pipeline page
- compute activeRunnable node-id once, reuse for the active-edge set
and the optimistic badge (flattened ternary); trim narrating docs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: live read-lineage overlay for inferred body assets
Renaming e.g. duckdb read_parquet('s3://...') / loadS3File now updates
the asset->reader edge live instead of only after Save re-derives the
persisted asset rows.
- extractReads() (+ shared refsByAccess) mirroring extractWrites
- inferredReadsByPath sticky cache, filled by handleAssetsChange and
the load prefetch alongside writes
- replace the write-only overlay loop with one overlayLineage(map,
access) helper invoked for both 'w' and 'r' (net DRY)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: detect S3 assets passed as SDK object arg in ts parser
Mirrors merged PR #9181 so feat/asset-graph-view is self-contained
(local origin/main is stale and lacks it). Object/{ s3, storage }
form of writeS3File/loadS3File is now detected, not only the bare
s3:// string literal.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: regenerate wasm Cargo.lock + frontend package-lock
Lockfile churn from local wasm-pack (asset target) + npm operations
during the asset-graph work. No source/dependency-intent change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: revert to bezier graph edges; add parsing-assets hint
The sugiyama-waypoint routing looked worse than the original; revert
AssetGraphEdge/assetGraphLayout to the pre-routing bezier logic (same
as the flow editor's BaseEdge) and drop the now-unused route plumbing
from the canvas. Add a small 'Parsing assets…' hint shown while the
load-time prefetch sweep is still inferring folder scripts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: extract pure resolveGraph merge + unit tests
Move the ~230-line graphWithDraft precedence/merge (base < session-
inferred < draft-seeded < open-script-live, +read/write/annotation
overlays, +dedup) out of the 1648-line route into a pure, testable
resolveGraph() module; the route's graphWithDraft is now a thin
$derived. Behaviour extracted verbatim. 10 unit tests cover the
precedence matrix. Phase 1 of the state/render split.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style: graph controls top-right, lift minimap, hide Save when unchanged
Controls -> top-right horizontal, no lock toggle; MiniMap !mb-10 so
it clears the activity bar; hide the per-script Save button when the
script is already at its latest save point (drafts still show Create).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: scope runtime-asset prune by id to spare static lineage rows
prune_runtime_assets deleted by (workspace_id, path, kind) tuple, so
trimming surplus usage_kind='job' rows for an s3 path also wiped the
static usage_kind='script'/'flow' producer rows for the same path —
silently breaking the asset-trigger cascade (fetch_producer_writes
found no writes; downstream never dispatched; required band-aid
re-syncs). Delete the surplus job rows by id instead; the inner query
is already scoped to usage_kind='job'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: don't re-pulse already-running jobs after they finish
The catch-up pulse re-added a completed job to the active set if its
start was within the (lagging) lookback window — even one we'd already
animated the whole time it ran — keeping its edges lit ~a poll
interval past completion (~5s after a 3.5s test). Track job ids seen
in-flight and skip the pulse for them; it still fires for hops whose
whole lifetime fell between two polls. Bound the set in lockstep with
eventsById; cleared on dispose.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: don't catch-up-pulse the runnable launched from the graph
If the poll never sampled a launched run's in-flight window, the
catch-up pulse re-flashed its edges one tick after it correctly
stopped (the page already animated it zero-latency via activeRunnable).
arm(launchedId) records the launched runnable id; catch-up skips it.
Cascade hops (other ids) still pulse. launchedIds cleared on stop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style: nudge graph controls left to clear panel toggle
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: partition value resolver + asset-cascade propagation
windmill-common/partition: pure resolver — time kinds (tz/format/start
anchor) + dynamic $.a.b JSONPath; 9 unit tests. asset_dispatch:
read the producer's resolved partition and thread it into every
cascaded subscriber's args + trigger.partition, so a chain resolves
once at the top. No migration (cascade needs no spec lookup). Stage
1+3 of pipeline partition runtime; run-start resolution is Stage 2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: show args form in compact pipeline preview when script has inputs
AssetGraphDetailsPane keeps the compact (hideArgs) preview but, via a new
previewPanel.argsAboveLogs flag, renders a compact SchemaForm between the
floating Test button and the logs/result panel when the script declares
inputs (e.g. a partitioned script needing a `partition` arg). The preview
pane also grows ~18pts so the args form doesn't shrink logs/result.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat: parser join-mode (`// trigger all`) + script_trigger.join_all
Stage A: JoinMode{Any(default),All} + `// trigger any|all` directive in
parse_pipeline_annotations; TriggerSpec::is_partition_bearing() (path
contains {partition}); join_mode threaded through all 4 asset-parser
crates (ts/py/sql/yaml). Stage B: reversible migration adds
script_trigger.join_all; insert_script_trigger writes it; deploy path
sets it from the parsed annotation. No reader yet (AND-join dispatch is
the next stage) so runtime behaviour is unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat: resolve pipeline partition at job execution time
Stage C: in handle_code_execution_job, once the script content is loaded,
parse the // partitioned annotation (free here) and resolve the concrete
partition once — schedule fire-time (scheduled_for anchor, not wall-clock)
for time kinds, triggering payload for dynamic. The value is injected
into the in-memory args the body sees (via a shadowed job clone) and
persisted back to v2_job.args so dispatch_asset_triggers propagates the
same value down the cascade. Already-set (explicit/backfill/cascade)
partitions are never re-resolved (run identity immutable); unresolvable
partitioned runs fail with a clear error. Integration test exercises the
full worker loop + cascade propagation.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat: AND-join barrier for partitioned pipeline subscribers
Stage D: a // trigger all subscriber no longer fires on any input. New
join_pending_inputs slot table keyed (workspace, subscriber, partition);
fetch_subscribers now returns join_all and the dispatch loop records each
partition-bearing input arrival, pushing the subscriber once only when
every partition-bearing input it declares is present for that partition.
Per-partition slots, cleared on fire (re-accumulate, no double-fire),
skew-immune (unlike debounce). Case-3 guard: an unpartitioned producer or
a reference (non-{partition}) input never fires a partitioned join.
Integration test covers wait/fire/isolation/no-double-fire.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat: opt-in // debounce for asset-cascade subscribers (parser + schema)
Stage E1+E2. Parser: script-level // debounce <dur> + per-// on
debounce=<dur> override (edge wins, else script default, else none =
fan-out, unchanged); TriggerSpec::Asset carries the per-edge override;
split_trailing_kv_opts separates the ref from trailing key=val opts.
Schema/deploy: reversible migration adds script_trigger.debounce_s;
parse_duration_secs (bare int or <n>s|m|h|d, fail-safe on garbage)
resolves the effective per-edge window at deploy and writes it per row.
No reader yet (dispatch wiring is E3) so runtime is unchanged. New unit
tests for the parser directive and duration parsing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat: apply opt-in debounce to asset-cascade subscriber dispatch
Stage E3. fetch_subscribers now also returns debounce_s; push_subscriber
builds real DebouncingSettings (delay + a (subscriber, partition) key,
so distinct partitions never collapse and latest-in-window falls out)
instead of ::default() when the edge opted in. Default stays no-debounce
(fan-out — the prior deliberate behaviour, now overridable rather than
reversed). Wiring test asserts the dispatched job carries the configured
window/key and an undebounced edge carries none.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: atomic AND-join gate + preserve resolved partition; drop scratch artifacts
Addresses local-review findings before PR:
- P1: record_and_check_join_slot was a non-atomic check-then-act on a
pooled connection; concurrent completion of a subscriber's last two
partition-bearing inputs on different workers could double-dispatch.
Now one transaction guarded by a tx-scoped advisory lock keyed on
(workspace, subscriber, partition) so the gate fires exactly once.
- P2: the preprocessed-args overwrite in result_processor replaced args
wholesale, dropping a partition resolved by resolve_partition_for_job;
the UPDATE now preserves an existing persisted partition key.
- P2: gate resolve_partition_for_job on a cheap code.contains check so
non-pipeline script jobs skip the annotation scan on the hot path.
- P2: remove 40 scratch screenshot PNGs, a flicker-debug script and a
local scheduler lock accidentally committed; gitignore the lock.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test: AND-join fires once under concurrent upstream completion
Regression for the check-then-act race fixed by the advisory-locked
transactional gate: releases N producer dispatches simultaneously via a
barrier and asserts the AND subscriber is pushed exactly once and the
slot is cleared. The invariant holds for the correct gate regardless of
interleaving; a non-atomic regression fails it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test: fuller partitioned join + multi-hop pipeline coverage
Exercises a complex pipeline combining options end to end: two
partitioned producers fanning into a // trigger all join, then a
multi-hop downstream chain. Asserts the resolved partition propagates
unchanged at every hop, chain depth increments per hop, the AND barrier
fires exactly once, and a second partition opens an independent slot
with no cross-partition bleed across the whole graph.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor: simplify pipeline code per review (dedup, single-parse, constant)
- ParseAssetsOutput::new() collapses the 6-line annotation copy-paste
across the 4 asset-parser crates to one call site.
- asset_dispatch: parse the cascade trigger object once and pass it to
the depth/partition readers instead of deserializing it twice; add a
TRIGGER_ARG constant for the previously stringly-typed key (3 sites).
- scripts deploy: drop a redundant debounce_default clone.
No behavior change; 29 parser + 6 dispatch integration tests green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat: reap abandoned AND-join slots after a TTL (default 60d, per-slot)
join_pending_inputs slots are normally cleared when the join fires;
partial slots whose inputs never all arrive (upstream removed/renamed,
one-off dynamic partition key, permanent skew) would otherwise leak.
windmill_queue::asset_dispatch::reap_stale_join_slots, called from the
monitor's delete_expired_items loop, deletes a (workspace, subscriber,
partition) slot only when its MOST RECENT row is older than
JOIN_SLOT_TTL_SECS (60d) — per-slot, never per-row, so a legitimately
slow join is not corrupted mid-accumulation. Conservative default;
per-join configurable TTL via the annotation is a planned follow-up.
Test covers stale-reaped / fresh-kept / mixed-slot-kept.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* update
* feat: path-less native trigger markers + missing-trigger placeholder
* feat: pipeline // tag and // retry annotations + dispatch_event log
* fix: derive test-pane min from split-axis dimension (height in bottom layout)
* feat: show last run logs/result when a script node is selected
* fix: backfill asset rows from script.assets for pre-feature scripts
* feat: job-id link + dispatch popover above script log/result
* style: drop 'dispatched' label, keep just the check icon
* fix: drop tag picker from pipeline script editor (set via // tag annotation)
* Nicer UI
* refactor: move google ai proxy handling to windmill-ai (#9260)
* refactor: add ai proxy execution mode
* refactor: move google ai proxy handling
* refactor: share google ai request building
* fix: early return should consider failure_module result (#9241)
* fix(flows): flag noLogs jobs and lazily resolve them in log panel (#9099)
* fix(flows): flag noLogs jobs and lazily resolve them in log panel
* fix appending to flag
* fix: preserve WM_LOGS_SKIPPED sentinel on SSE/replay completion
pickMoreCompleteLogs resolved both sentinel and undefined to '', so the
SSE completion event (whose job field is fetched .without_logs()) would
clobber the sentinel placed by flagSkippedLogs. The module log panel
then saw '' instead of the sentinel, defeating the lazy-resolve path.
Also wire onLogsResolved on the OutputPickerInner inline LogViewer so a
lazy resolve writes back to flowStateStore.previewLogs, matching
ModulePreviewResultViewer and avoiding repeated fetches on remount.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(main): release 1.705.0 (#9229)
* chore(main): release 1.705.0
* Apply automatic changes
---------
Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
* chore: add playwright mcp for frontend verification (#9269)
* feat: CLI datatable serve / psql (#9267)
* feat(cli): add datatable list and run commands
* feat(cli): render datatable query results as a table
* feat(cli): serve datatables as a postgres-wire endpoint
* feat(cli): add 'datatable psql' to launch psql against the proxy
* feat(cli): route datatable serve by client-supplied database name
* override database list + password option
* fix: support extended queries in datatable serve
* fix: correct cloud size threshold log and parse CLI descriptions with parens/trailing comma
* refactor: extract raw_output envelope encoding into pg_raw_output module
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
* oom_adj nit
* feat: add UV_PYTHON_INSTALL_MIRROR env and instance setting (#9271)
* feat: add UV_PYTHON_INSTALL_MIRROR env and instance setting
Allows operators to point `uv python install` at a private mirror of the
python-build-standalone releases. Configurable via the
`UV_PYTHON_INSTALL_MIRROR` env var or the `uv_python_install_mirror`
instance setting, with the env var as the boot fallback and the instance
setting taking precedence at reload.
Fixes WIN-1966
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: hoist uv_python_install_mirror binding above sandboxing branch
The non-sandboxed uv pip install branch referenced a binding that was
only declared inside the sandboxed branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: neutral placeholder for uv_python_install_mirror
The previous placeholder was the default public URL the setting is meant
to redirect away from. A neutral example mirror URL is clearer.
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(indexer): tell admins when ingress routes search to wrong pod (#9274)
* [ee] fix(indexer): tell admins when ingress routes search to wrong pod
When the IndexReader is absent on the pod handling a search request but
another pod is actively holding the indexer lock, the EE handler now
returns a tailored error pointing at the ingress/load-balancer
configuration instead of the generic "indexer not running" message.
The indexer status endpoint reads the DB lock so it reports "running"
from any pod, but search endpoints need the in-memory IndexReader that
only exists on the lock holder. In multi-replica deployments this looks
like the indexer is healthy but every search 404s.
Companion: windmill-labs/windmill-ee-private#TBD
Fixes WIN-1968.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to eb18d7b4c0e37fea3f6e1e2cc44e0fddd74ff817
This commit updates the EE repository reference after PR #586 was merged in windmill-ee-private.
Previous ee-repo-ref: 7dd43d1850813071cc18ba49ba090583e7321f4b
New ee-repo-ref: eb18d7b4c0e37fea3f6e1e2cc44e0fddd74ff817
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>
* feat(cli): add `wmill init prompts` and custom override slot (#9266)
* feat(cli): add `wmill init prompts` and custom override slot
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): replace init prompts with refresh prompts + AGENTS.md/AGENTS.cli.md split
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): dedupe claude skills via @-includes and add prompts freshness check
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): drop migration-choice flags from `refresh prompts`
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(cli): add 'Running and previewing local changes' section to AGENTS.cli.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): write full skill content to .claude/, drop @-include wrapper
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): reconcile CLAUDE.md the same way as AGENTS.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): address PR review nits — argv parsing, lazy import, comment detection, error propagation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add yolo mode for ai chat tools (#9258)
* feat: add yolo mode for ai chat tools
* nit
* fix: align chat footer controls
* feat: add ai chat autonomy modes
* feat: add autonomy mode dropdown
* fix: highlight yolo autonomy icon
* fix: auto accept flow edits
* fix: hide unsupported autonomy modes
* fix: handle auto-accept flow editor races
* fix(debugger): add non-root user support to Dockerfile (#9277)
Mirrors the main Windmill Dockerfile pattern: creates a windmill user
(UID/GID 1000) and makes cache/work directories world-writable so the
image runs cleanly under Kubernetes securityContext.runAsNonRoot or
runAsUser: 1000 without permission errors on Bun, pip, or windmill
cache writes.
Fixes WIN-1969
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai): enforce RLS and scope check on user-supplied X-Resource-Path (#9276)
* fix(ai): enforce RLS and scope check on user-supplied X-Resource-Path
The AI proxy handler accepts an X-Resource-Path header to override the
configured workspace AI provider. When supplied, the handler loaded the
resource value from the resource table using the root DB pool with no
resources:read scope check, so any authenticated workspace user could
point X-Resource-Path at a restricted AI resource (e.g. one in a folder
they cannot read) and the proxy would use that resource's provider
credentials for the outbound AI request.
For user-supplied resource paths, now require resources:read:{path}
scope and fetch the resource through user_db.begin(&authed) so RLS
enforces the same folder/group boundary as the resource API. The RLS-
scoped $var: resolution stays in place as defense in depth. The
admin-configured workspace/instance ai_config path is unchanged.
Fixes WIN-1971
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(ai): regression test for X-Resource-Path RLS enforcement
Cover all four cases:
- non-admin pointing X-Resource-Path at a restricted resource is rejected
- non-admin pointing it at a resource they own still works
- admin can point it at any resource
- workspace-configured proxy flow (no X-Resource-Path) is unchanged
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add userdraft listing primitives (#9268)
* feat: add userdraft listing primitives
* fix: cancel stale userdraft discard writes
* docs: remove global ai userdraft plan
* feat(nsjail): optional disk-backed /tmp via instance setting (#9272)
* feat(nsjail): optional disk-backed /tmp via instance setting
* test(nsjail): unit-test tmp mount resolver and narrow visibility
* refactor(nsjail): switch tmp backing to select + conditional UI
* ui(nsjail): make tmpfs the visible default in /tmp backing select
* fix(nsjail): refuse preexisting jail_tmp to block symlink escape
* fix(nsjail): allow jail_tmp reuse on sequential nsjail calls
Codex flagged that python/ruby/rust executors invoke nsjail twice per
job_dir (install then run). The previous resolver treated any preexisting
jail_tmp as hostile and silently fell back to tmpfs on the second call,
so disk-backed mode never reached the main script run for those langs.
Use symlink_metadata().is_dir() to distinguish a real directory left by
an earlier call in the same job_dir (safe to reuse) from a symlink or
other entity (still refused, as the codebase-tar escape requires).
Also loosen the frontend visibility predicate: only hide nsjail settings
when job_isolation is explicitly 'none' or 'unshare', so deployments
that enable nsjail via DISABLE_NSJAIL=false with no DB setting can
still see the controls.
* chore(main): release 1.706.0 (#9270)
* chore(main): release 1.706.0
* Apply automatic changes
---------
Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
* fix(nsjail): gate unix-symlink test behind cfg(unix) for Windows build (#9280)
The disk_backed_refuses_preexisting_symlink_at_jail_tmp test calls
std::os::unix::fs::symlink directly, which doesn't exist on Windows
targets. Without a cfg gate, `cargo check --tests` fails on Windows
with E0433. Other symlink call sites in this crate (php_executor,
bun_executor, rust_executor, etc.) already follow this pattern.
Fixes WIN-1972
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Reduce slim image vulnerability surface (#9279)
* Reduce slim image vulnerability surface
* chore(docker): drop apt-get upgrade -y from slim images
apt-get upgrade hurts build reproducibility (same Dockerfile + same
commit at different times produces divergent images) and trips hadolint
DL3005. The freshness it buys is dominated by simply rebuilding against
the periodically-refreshed debian:bookworm-slim base image.
The --no-install-recommends and apt-list cleanup wins are kept.
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* fix(git-sync): bump to hub/28234 with stateless gpg.program wrapper (WIN-1974) (#9282)
* fix(git-sync): revert LATEST_GIT_SYNC_SCRIPT_PATH to hub/28230 to restore GPG-signed deploys (WIN-1974)
hub/28231 (PR #9230) is the "thin" script that hands the actual `git commit`
to the CLI's hidden `sync git-deploy`. The hub script still does the GPG
setup (import key into a fresh GNUPGHOME, dummy `gpg -bsau` to warm the
agent passphrase cache, then `git config user.signingkey` + `commit.gpgsign`
locally), but the commit no longer runs in the same `git_push` flow — it
runs minutes later inside the CLI after workspace API resolution, zip pull,
file extraction, and lockfile autofill. By the time the spawned `git commit`
asks gpg-agent for the cached passphrase, the cache state is no longer
reliable (or the spawned `gpg` ends up talking to a fresh agent), so signing
fails non-interactively with `gpg failed to sign the data`.
hub/28230 is hub/28217's in-script logic rebuilt with windmill-cli@1.703.3:
the GPG setup and the in-script `sh_run("git commit ...")` happen back-to-back
in `git_push`, so the cache is always fresh. It preserves wm_deploy / fork
branch behavior, the EE deployment-callback `main()` signature is unchanged,
and the only min-version check in EE (`is_script_meets_min_version(28103)`)
is comfortably below 28230 — so this revert is safe.
Forward fix (separate PR): publish a new thin script that, alongside the
existing GPG setup, writes a `gpg.program` wrapper using `--pinentry-mode
loopback --passphrase-file` so signing is independent of the agent's cache
state. Re-bump past 28231 then.
Fixes WIN-1974
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(git-sync): check in source-of-truth for the next hub script (gpg.program wrapper)
This is the script that will be published to hub.windmill.dev once verified
on a customer GPG-signed deploy. It replaces hub/28231's agent-cache
pre-warm (`gpg -bsau` with --passphrase) with a stateless gpg.program
wrapper + chmod-600 passphrase file. Every git-invoked gpg call goes
through the wrapper, which always uses --pinentry-mode loopback (and
--passphrase-file when a passphrase exists). Signing no longer depends on
gpg-agent having a cached passphrase by the time the CLI's `git commit`
runs — which closes WIN-1974.
Not wired in yet: LATEST_GIT_SYNC_SCRIPT_PATH stays on hub/28230 until this
script is uploaded and the new hub id is known. This file is checked in so
the diff is reviewable, future bumps have a source of truth, and a CLI
regression test can `cat` it for fixture parity.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): skip format/pattern validation for $var/$res/$jsonvar references in ArgInput
A resource field with a `pattern` constraint (e.g. the gpg_key.private_key
field, whose pattern enforces a `-----BEGIN PGP PRIVATE KEY BLOCK-----`
prefix) rejects values like `$var:u/me/gpg-private-key` with an "invalid
format" error in the resource editor — even though `$var:`/`$res:`/`$jsonvar:`
are placeholders the backend resolves at runtime, not the actual string
that needs to match the regex.
Bail out of all format/pattern checks (email, ipv4, ipv6, uuid, custom
pattern) when the value is one of these references. Required/numeric
bounds/array checks still apply since they're shape-level, not regex.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(git-sync): bump LATEST_GIT_SYNC_SCRIPT_PATH to hub/28234 (gpg.program-wrapper fix)
hub/28234 is the forward fix for WIN-1974: replaces hub/28231's agent-cache
pre-warm (which became stale by the time the CLI's `git commit` ran) with
a stateless `gpg.program` wrapper that uses `--pinentry-mode loopback`
(and `--passphrase-file` when a passphrase exists) on every gpg invocation.
Bundled CLI is windmill-cli@1.705.0.
Verified via reproducer at /tmp/git-sync-diff/test-gpg-fix.sh: deliberately
killing gpg-agent between GPG setup and `git commit` reproduces the
customer's `gpg failed to sign the data` error verbatim under the old
flow, and the wrapper signs through it. Holds for passphrase-protected
keys, split-subkey [C]+[S] layouts, and unprotected keys.
Drops the local source-of-truth copy (`hub-scripts/`) — hub is canonical
now that 28234 is published.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(git-sync): drop verbose comment above LATEST_GIT_SYNC_SCRIPT_PATH
The git history (this PR) carries the why; the constant name + value carry
the what.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): wmill sync git-deploy stops committing; caller owns commit+push (#9284)
Single contract for the deployment-callback path: the CLI does branch
checkout + pull, the caller (hub script in production, test in test)
does git add + commit + push. This restores the WIN-1974 invariant —
GPG setup and `git commit` run back-to-back in the same process, so
the agent's pre-warmed passphrase cache is still warm at sign time —
without needing a `--skip-commit` flag for the hub case and a default
"also-commit" for everything else. Same behavior in every call site.
Changes:
- sync.ts: drop the gitSyncDeployPush call from pull()'s deploy path
(both the onlyCreateBranch fast-return and the post-pull commit).
`gitSyncDeployPush` stays exported for any caller that wants the
same commit/push semantics — just not invoked by the CLI subcommand.
- gitsync_promotion.test.ts: e2e test now does its own git add +
commit + push after `wmill sync git-deploy`, mirroring what the
hub script does in production. Same regression coverage
(wm_deploy branch created in Case A, main untouched; main updated
in Case B, no new wm_deploy).
CLI typecheck unchanged (two pre-existing TarAsZip errors at lines
2578/3307, present before this PR). All 743 unit tests still pass.
The accompanying hub script (option-C — CLI for branch+pull, script
for commit+push) lives at /tmp/git-sync-diff/sync-script-to-git-repo-windmill.option-C.ts.
Once published, a follow-up bumps LATEST_GIT_SYNC_SCRIPT_PATH to its id.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* bump git sync to 28236
* fix: fork compare visibility for non-admins and stale-token superadmins (#9283)
* fix: use fork-scoped authed for fork visibility in compare_workspaces
* test: add EE end-to-end repro for fork rename visibility
* chore: restore concurrency_locks sqlx cache lost in cleanup
* test: add regression for stale-superadmin-token fork visibility bug
* chore: update sqlx cache for new test queries
* chore(main): release 1.706.1 (#9281)
* chore(main): release 1.706.1
* Apply automatic changes
---------
Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
* feat: add wmill job rerun subcommand (#9275)
* feat: add wmill job rerun subcommand
* feat: add wmill job restart subcommand for flow restart-at-step
* chore(system_prompts): point plugin skills sync at plugins/windmill/ (#9287)
* chore(system_prompts): point plugin skills sync at plugins/windmill/
The plugin checkout's plugin folder is being renamed from
`plugins/windmill-code-plugin/` to `plugins/windmill/` to shorten the
slash-command namespace and align with the matching Cursor plugin
layout.
Paired with windmill-labs/windmill-claude-plugin#8. That PR must merge
first so the next sync run finds the new folder.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(system_prompts): update plugin-dir example to plugins/windmill
Co-authored-by: centdix <centdix@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: centdix <centdix@users.noreply.github.com>
* fix(cli): wmill sync pull updates wmill-lock.yaml for raw apps (#9289)
* fix: flow recording teardown crash + rename package to @windmill-labs/components (#9288)
* fix: guard against null recording during FlowRecordingReplay teardown
Navigating away from a flow recording inside a workspace file-tree view
threw `TypeError: Cannot read properties of null (reading 'flow')` from
FlowGraphViewer once during the teardown tick.
Svelte 5 compiles child component props as live getters that close over
`$$props.recording.flow`. When `recording` flips to null on the parent's
navigation, an outer `{#if !recording?.flow}` doesn't stop those getters
from firing one more time as derived effects re-evaluate before the
unmount lands — so the getter dereferences null and throws.
Fix at the two layers where the deref actually happens:
- FlowRecordingReplay: use `recording?.flow` at the binding sites
(FlowViewer + graph-snippet FlowGraphViewer) so the compiler emits an
optional-chained getter, and guard the snippet branch with
`{:else if recording?.flow}` so it doesn't mount when there's nothing
to show.
- FlowGraphViewer: finish the optional chaining the rest of the file
already used everywhere else (`flow?.value?.skip_expr`,
`flow?.value?.cache_ttl`, `flow?.schema`). When the upstream
binding returns undefined during teardown, the graph degrades to an
empty frame instead of crashing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: rename package to @windmill-labs/components
- frontend/package.json: rename `windmill-components` → `@windmill-labs/components`
- frontend/publish.sh: drop the in-place sed rename dance; the checked-in name now matches what's published, so `npm run package && npm publish` is enough
- frontend/package-lock.json, system_prompts/auto-generated/prompts.d.ts: regenerated by `npm run package` under the new name
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* default script name
* save logic
* Keyboard nav
* finish keynav
* nits
* CI fix
* nit stop propagation
* Merge branch 'main' into feat/asset-graph-view
* commit
* update
* fix: cropped save button on small screens
* progress
* managed scheduled removed
* all
* progress
* feat: add data upload pipeline trigger with auto S3 picker
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: avoid pane editor remount flicker when deploying a pipeline draft
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: show only the edited script's I/O in the asset graph, not the saved version's
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: derive script asset rows server-side at deploy
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: shared fixture corpus keeps annotation parsers in parity
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: dev-run draft pipeline chains, live badges, deploy drift warning
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: ungate cascade producers, squash pipeline migrations
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: drop committed cli-sync fixtures and stray screenshots
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: show skip-asset-dispatch flag as badge instead of args row
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: pipeline view mode default with activity feed, drafts overlay chip
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: treat DROP TABLE as table-level write in sql asset parser
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: wmill datatable create + actionable sql extension error
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: ephemeral data-pipelines demo sync repo zip for handoff
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: wmill pipeline list/show renders the asset DAG in the terminal
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* nits
* nits
* nits
* nits
* fix: defer draft persist-back past the batch so discard sticks first click
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: band-reserving tidy-tree asset graph layout with join breakpoints
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: route skip-layer and long graph edges around occupied columns
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: seed s3 template outputs with canonical leading-slash paths
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* all
* feat: bundle data-pipeline drafts into the DB-backed user draft system
Pipeline drafts were browser-only (localStorage `pipeline-<folder>`), so they
didn't sync across devices, weren't server-visible, and never showed in the
drafts list. Store them instead as one per-user `draft` row of a new
`data_pipeline` kind, keyed at the folder (`f/<folder>/data_pipeline`), holding
the same `{ drafts, activeDraftPath }` bundle.
Stage 1 — backend kind: add `data_pipeline` to DRAFT_KIND (migration) and
`UserDraftItemKind` (deployed_table=None, private). The list/update handlers
and folder-path access check already cover a backing-table-less kind.
Stage 2 — sync: add `GET /drafts/get_own/{kind}/{path}` so an editor with no
deployed-overlay GET can load its own draft. The pipeline page now hydrates
from the DB on mount (one-time localStorage import for in-flight drafts) and
persists via UserDraftDbSyncer (debounce + optimistic-concurrency), keeping a
localStorage crash mirror.
Stage 3 — surface: the drafts review page renders the bundle as a "pipeline"
row that opens `/pipeline/<folder>` (open-only; excluded from bulk deploy).
Verified end-to-end in-browser: DB-seeded draft hydrates to "Edit (1)", edits
persist back, and the row shows with Open pipeline / Discard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: pipeline Activity panel grouping, run↔graph highlight, deploy-conflict handling
Activity panel (view mode):
- Group cascade runs by the connected component of the asset-dispatch graph
(new GET /jobs/asset_dispatch_edges over the dispatch_event table, incl.
join_pending inputs), headed by the earliest originating run + its trigger,
with a "+N" chip for joins fed by multiple triggers.
- Success/failure count histogram with drag-to-filter brushing, an always-on
time axis + per-bar tooltips, a Reset, and Last hour/24h/48h/7/30/90d ranges.
- Node run-count/status badges now derive from the same merged historic+live
events the panel shows (previously session-only).
Run ↔ graph highlight:
- Hovering a run row (or a group header → the whole cascade) rings the
node(s), animates their incident edges, and borders the adjacent assets in
the edge hue (blue write / gray read); expanding a run pins a soft-blue ring.
- Switching edit→view re-surfaces the Activity feed.
Deploy:
- Live-content autosave for the open pipeline draft + an autosave indicator.
- Re-saving a script now chains off the hash just created instead of a stale
parent_hash (fixes the "lineage must be linear" error on a second save), and
a genuine concurrent deploy opens a keep-mine / view-latest conflict modal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: pipeline editor badge requires asset-parse, not just main-function parse
A pipeline script's asset lineage is load-bearing — a deploy that can't parse
assets silently records no edges. The editor "parsable" dot only reflected
inferArgs (the main function), so a body the asset parser rejects (e.g. a
trailing `/////` in DuckDB) still showed green and deployed with empty lineage.
ScriptEditor gains `requireValidAssets` (set by the pipeline pane); when on, the
EditorBar badge is green only if BOTH the main function and inferAssets parse,
with the tooltip distinguishing "Main function not parsable" / "Assets not
parsable" / "Parsable".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: route asset-graph edges around nodes that sit in their path
Edges could draw straight through an unrelated node (a join fan-out or long
cross-component edge), making it ambiguous whether that node shared the input.
AssetGraphEdge only saw its own endpoints, so it could only detour the
near-vertical same-column skip case.
The canvas now (once per layout, O(edges × nodes) — no per-frame cost) samples
each edge's straight run against every non-incident node center and, on a
crossing, passes a clear gutter lane to the edge via `data.detourX`;
AssetGraphEdge routes the rounded-orthogonal detour through it. Verified: 0
edge↔node box crossings on the orders pipeline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: deploy pipeline drafts with freshly-inferred assets, not a stale snapshot
"Save all" spread `...draft.script` into createScript, which carries a `assets`
snapshot that isn't refreshed when the body is edited. So a renamed/removed
output (e.g. an old `CREATE TABLE exciting_en32z9` later changed to
`exciting_880909`) was re-deployed as a phantom write edge and lingered as an
orphan asset on the graph — shown with no producer, and shifting position on
click as the graph re-derived.
saveDraft now re-runs inferAssets on the current body and passes the result as
`assets`, overriding the snapshot — mirroring the per-pane save. The backend
clears+reinserts from the sent set, so a re-deploy drops the stale rows.
Verified: deploying with the fresh asset set removes the orphan from the graph.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: collect upstream reads from CTAS and CREATE VIEW in SQL asset parser
`CREATE TABLE x AS SELECT … FROM y` (and `CREATE VIEW`) recorded only the
write to x — the source read of y was silently dropped. Table-level reads are
gathered in the `Statement::Query` arm via handle_table_with_joins; the generic
table-factor visitor only picks up read-functions and string literals, not
plain `FROM <table>` references. The AS-query of a CTAS isn't a
`Statement::Query`, so its FROM tables were never walked. On the pipeline
canvas this meant a `datatable://…` upstream consumed by a CTAS step showed no
read node/edge — the step looked like it produced its output from nothing.
Factor the Query arm's read collection into handle_query_reads and call it from
the CreateTable (when it has an AS-query) and CreateView arms, balancing the
cte_name_stack push in post_visit_statement. Updated the drop_then_create test
(which had pinned the old drop-the-read behavior) and added CTAS + CREATE VIEW
read coverage. Verified against the rebuilt asset wasm: the live editor now
infers the read.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* update
* updates
* refactor: dedup asset-graph code, squash migrations, drop artifacts
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf: gate asset dispatch on a cached per-workspace producer set
Cache the producer-path→writes map per workspace and invalidate it from the asset-clear paths via the notify_event polling system, so a top-level script/preview completion that isn't an asset producer costs an in-memory lookup instead of a per-completion query.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: remove dead unquote fn that failed backend check under -D warnings
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: green the frontend check (pin published wasm-asset, fix type errors)
Pin windmill-parser-wasm-asset to the published 1.728.1 (was a file: link to a gitignored, CI-unbuilt pkg-asset). Exclude test files from svelte-check (the parity test reads a backend fixture via node:fs, which the browser app tsconfig has no @types/node for; vitest still runs them). Fix pre-existing branch type errors: drop the unsupported 2nd getScriptByPath arg, cast script.schema to Schema for inferArgs, coerce has_preprocessor to a definite boolean, and wrap the cancelJob handler so it isn't possibly-undefined.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: move pipeline partition resolution to ee-private (free-CE)
Partition resolution becomes a private module (partition_ee in windmill-ee-private, hidden from the public repo) with an OSS no-op fallback (partition_oss); call sites resolve via the aliased windmill_common::partition. Not enterprise-gated — free to run in CE. Bumps ee-repo-ref to the ee branch carrying partition_ee. Verified building in default, private, and private,enterprise (offline). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: move asset-cascade join/debounce/retry to ee-private (free-CE)
Join barrier, debounce, and retry become the private windmill_queue::cascade module (cascade_ee in windmill-ee-private); OSS gets cascade_oss no-op fallbacks (plain OR fan-out). Core cascade stays public. Bumps ee-repo-ref. Verified default/private/private,enterprise. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: skeleton enterprise pipeline freshness + backfill (TODO, ee-private)
Gated windmill_common::pipeline_advanced (private; pipeline_advanced_ee) with OSS fallback; entry points return a clear not-implemented error. Deploy surfaces a TODO when a script declares // freshness. Bumps ee-repo-ref. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: repair asset_trigger_dispatch test after cascade carve-out + cache its queries
Stage-2 moved reap_stale_join_slots to windmill_queue::cascade; update the integration test's import. Also commit the test's sqlx query cache (was never prepared with --tests, so SQLX_OFFLINE cargo test failed pre-existing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: invalidate producer-cache in asset dispatch tests (mirror deploy)
The tests seed asset rows directly and run no notify poller, so the per-workspace producer cache went stale across tests → 0 dispatched. Clear it at the seed point, as a deploy would via notify_event. All 8 asset_trigger_dispatch tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to ba677ea142011462ad4dfe77e8375a6dd274cdef
This commit updates the EE repository reference after PR #619 was merged in windmill-ee-private.
Previous ee-repo-ref: 925c350cff55d3ea738d9e2e4098d9ce4bdda418
New ee-repo-ref: ba677ea142011462ad4dfe77e8375a6dd274cdef
Automated by sync-ee-ref workflow.
* test: disable producer cache in asset dispatch tests (isolated-DB safe)
The .remove(WS) approach still raced: #[sqlx::test] gives each test its own DB but they share one workspace id, so the WS-keyed process-global cache clobbered across DBs under concurrent threads. Add an ASSET_PRODUCER_CACHE_DISABLED test hook and set it in the tests so every dispatch reads its own DB. 8/8 pass at --test-threads=10. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: replace asset-cascade depth cap with cycle detection
The hardcoded MAX_CHAIN_DEPTH=5 truncated legitimate deep pipelines (silently — the check returned before event logging). Replace it with per-edge cycle detection: carry the producer lineage in trigger.chain and skip only a subscriber already in the chain, recording a visible cycle_detected dispatch_event. Acyclic pipelines of any depth now cascade fully; a high MAX_CHAIN_LEN backstop guards against runaway. Tests + UI label updated; 8/8 pass at --test-threads=10.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: update dispatch_event reason examples (depth_cap → cycle_detected)
Comment-only; the migration is idempotent and already in the potentially_stale self-heal list, so the checksum change re-applies cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: park cascade retry (P1 dead-end) + clear stale script_triggers on rename
Two deploy-path fixes:
- Retry is parked: a retried subscriber is wrapped in a SingleStepFlow, whose run is a flow step and ineligible for asset dispatch, so it would silently dead-end the cascade (P1). Stop persisting retry to script_trigger and warn at deploy; TODO(pipeline-retry) to re-enable once dispatch handles flow-wrapped producers. (Dispatch plumbing kept + still tested via direct seeding.)
- Rename leaves stale script_trigger rows: clear was keyed on ns.path only, so old-path '// on' edges lingered and could trigger a script later recreated at that path. Also clear the old path on rename (assets already handled via the parent-hash clear).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
Co-authored-by: hugocasa <hugo@casademont.ch>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Arnaud <31803803+Araden14@users.noreply.github.com>
Co-authored-by: Diego Imbert <diego@windmill.dev>
Co-authored-by: centdix <40307056+centdix@users.noreply.github.com>
Co-authored-by: Diego Imbert <70353967+diegoimbert@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Aldrin Jenson <aldrinjenson@gmail.com>
Co-authored-by: centdix <centdix@users.noreply.github.com>
NetSuite is a per-instance OAuth provider (account-specific authorize/token
URLs), registered via connect_config_template. Its authorize endpoint
requires scope=rest_webservices, so the template mechanism gains an
optional scopes field copied into the built connect_config.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(workspaces): add skip_email option to invite_user and add_user endpoints
The workspace invite_user and add_user API endpoints unconditionally sent
notification emails when SMTP was configured, with no way to suppress them
per-request. This is noise for automated workflows that programmatically add
users to workspaces.
Add an optional `skip_email: Option<bool>` field to `NewWorkspaceInvite` and
`NewWorkspaceUser`, following the existing pattern on `NewUser` used by
POST /api/users/create, and guard the `send_email_if_possible` calls with
`if !nu.skip_email.unwrap_or(false)`. The field is optional, so existing
clients are unaffected.
The auto-add code paths in workspaces_ee.rs (domain-based and instance-group
auto-add) are auto-triggered and take no API parameter, so they are left as-is.
Fixes WIN-2068
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(workspaces): make workspace invite/add emails toggleable via instance setting
Replace the per-request skip_email approach with an instance-level setting
`disable_workspace_invite_emails`. When enabled, the email notifications sent by
the workspace invite_user and add_user endpoints are suppressed. Useful for
instances where users are added programmatically (e.g. CI pipelines that fork
workspaces and add users) and the invite emails are noise.
Backend:
- Add `DISABLE_WORKSPACE_INVITE_EMAILS_SETTING` global setting constant.
- Guard the `send_email_if_possible` calls in invite_user and add_user with a
read of that setting (via the existing `load_value_from_global_settings`
helper). Defaults to false, so existing behavior is unchanged.
- Revert the per-request `skip_email` field on NewWorkspaceInvite /
NewWorkspaceUser and the corresponding openapi additions.
Frontend:
- Expose the setting as a boolean toggle in the SMTP tab of the instance
settings (superadmin).
The auto-add paths in workspaces_ee.rs are unaffected.
Fixes WIN-2068
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): gate disable_workspace_invite_emails toggle behind EE
Email delivery (send_email_if_possible) is a no-op outside the EE/private
build, so the toggle has no effect on a pure-OSS instance. Add `ee_only: ''`
to match the sibling SMTP settings: the toggle is grayed out (with an EE badge)
on non-EE instances instead of rendering as an active no-op control.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): don't EE-gate disable_workspace_invite_emails toggle
The earlier ee_only addition was based on the false premise that the
workspace invite/add emails are license-gated. They are not: SMTP
configuration (SmtpSettings) and email sending (send_email_if_possible)
have no enterpriseLicense check — they only require the closed-source
build with SMTP configured. The sibling smtp_settings carries ee_only: ''
but its smtp_connect field renders no SettingCard label, so that flag is
inert (no badge, no disable). On a plain boolean field ee_only is fully
active, which incorrectly grayed out the toggle and showed an EE badge.
Drop ee_only so the control matches the actual non-license-gated behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-chat): summary-based conversation compaction
Replace drop-oldest compaction with summary-based partial compaction: when
a send would cross the context-window trigger, summarize the older prefix
into one message and keep the recent tail verbatim, replacing the prefix in
both the model context and the visible transcript with a collapsible
boundary. Drop-oldest remains a fallback; a circuit breaker disables the
summary round-trip after repeated failures.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* nit
* fix(ai-chat): address review findings on summary compaction
- Stop during an in-flight summary no longer falls through to a destructive
drop-oldest compaction. The aborted controller short-circuits the fallback
and its save, so the cancel path rolls the unsent turn back cleanly instead
of permanently dropping older history (P1).
- Preserve the original chat title across compaction: once the summary
boundary leads the transcript, reuse the title computed before compaction
rather than re-deriving it from the first surviving tail message (P2).
- Strip every <analysis> block from the model's summary, not just the first,
so extra scratchpad blocks can't leak into context (P2).
- Reindent AIChatMessage.svelte / ContextUsageIndicator.svelte (prettier).
Adds regression tests for the abort path, title preservation, and
multi-analysis stripping.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* nit
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: restrict agent-worker global setting reads to an allowlist
Add AGENT_WORKER_READABLE_SETTINGS allowlist of the operational settings
agent workers load over HTTP, with a helper used by the agent endpoint to
reject any other key. Bump ee-repo-ref for the companion EE handler change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 3fab9f01ecce3dad0aa9b9c544d41f1e88bc81dd
This commit updates the EE repository reference after PR #615 was merged in windmill-ee-private.
Previous ee-repo-ref: 8a657066fda1c5ffe225588bce6c349cffd81e98
New ee-repo-ref: 3fab9f01ecce3dad0aa9b9c544d41f1e88bc81dd
Automated by sync-ee-ref workflow.
* fix: make agent-worker setting gate a blocklist instead of allowlist
Switch is_setting_readable_by_agent_worker to deny-by-exception: serve every
global setting to agent workers except AGENT_WORKER_BLOCKED_SETTINGS (the
instance secrets). Update tests and bump ee-repo-ref for the companion comment
change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: remind to blocklist new secret settings for agent workers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 9e4dadafb44ba953a7d2af2be12b92be98d86b66
This commit updates the EE repository reference after PR #618 was merged in windmill-ee-private.
Previous ee-repo-ref: 8e32afb69ffc4d5f080c0c4bc6b023d57d0f39ae
New ee-repo-ref: 9e4dadafb44ba953a7d2af2be12b92be98d86b66
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* oauth: map Coupa instance to instance_url resource arg
Coupa's managed client-credentials connect collects an instance name to
host-pin the token URL but had no resource_mapping, so the created resource's
instance_url (the API base URL the hub scripts build on) stayed empty. Add the
mapping, mirroring ServiceNow, so the entered instance fills it automatically.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* oauth: default Coupa client-credentials scopes (cc_scopes)
Prefill the connect dialog's scope field with the core.* scopes the Coupa hub
scripts exercise — read+write for suppliers/purchase_orders/requisitions/invoices,
read-only for contracts/expenses (the shipped scripts only read those). Scope
names verified against the Coupa scope docs and corroborated in production code.
The user can trim them to what their OIDC client is granted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): replace other-user draft "View JSON" with "View Diff"
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): replace other-user draft "Fork" with in-place "Load"
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(frontend): detect first overlay edit by value divergence, not a timer
Replaces the 700ms arming timer (which leaked across sessions and silently
swallowed sub-window edits) with a deterministic check: a blocked save opens
the overwrite prompt only once the cell value diverges from the loaded value.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): overlay leak on revisit, diff z-index, home-popover edit affordances
- Clear a stale "editing another user's draft" overlay when its editor is
reloaded without a fresh Load, so returning to the item edits our own draft.
- Open View Diff above the others-drafts modal (close it first) instead of
rendering the drawer behind it.
- Add an Edit button to our own row in the home draft popover; use a pencil
icon (not a download) for Load.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: admin "Migrate" action for legacy drafts (delete / assign to self)
Adds an admin-gated `POST /drafts/migrate_legacy/{kind}/{path}` endpoint to
resolve pre-migration workspace-level drafts (email NULL): delete the row, or
move its value onto the admin's own row. Surfaces a "Migrate" button on legacy
rows in the home-page draft popover and the in-editor others-drafts modal
(workspace admins / superadmins only), opening a modal with Delete and
Assign to self.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): close home draft popover before opening View Diff / Migrate
The hover popover sits above the diff drawer and migrate modal (z-index), so
it covered them. Close it first so they render on top.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): remount the flow builder on "Reset to draft" from an overlay
FlowBuilder captures the flow at mount, so reloading the value alone left the
foreign graph on screen — reset appeared to do nothing. Force a remount
(renderEditor=false → loadFlow) like navigation does. Scripts (imperative
setCode) and apps (redraw++) already remount, so only flows needed this.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): refresh the home row after migrating a legacy draft
invalidateAll() didn't refetch the home list (it loads items client-side), so
the legacy badge entry lingered after delete / assign-to-self. Bubble an
onMigrated callback up to the row's `change` event, reusing the same reload
chain (Item → ItemsList loadScripts/Flows/Apps) as delete/archive.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* nit
* nit
* fix(frontend): match app overlay baseline to the migrated value
AppEditor migrateApp()s the app on mount, so the draft cell settles to the
migrated value. The overlay used the raw loaded value as the divergence
baseline, so a post-mount mirror write could trip "Overwrite your current
draft?" before any edit. Migrate the baseline too (like the deployed-baseline
and raw_app bundle do) so it matches the settled cell.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): address review on legacy-draft migrate + overlay
- Legacy "Assign to self" now confirms before replacing an existing own draft
(MigrateLegacyDraftModal gains an `ownDraftExists` step, threaded from the
home badge and the in-editor others-drafts modal).
- Gate overlay mode on a per-response `hasOwnDraft` instead of the sticky
`loadedFromDraft`, so navigating to a no-own-draft item in the same editor
route can't wrongly enter overlay. Fixed in all 4 editor routes.
- Raw-app "View Diff" now projects the deployed app into the flat draft-bundle
shape (via a shared `extractDataConfig`) instead of diffing `.value` against
the bundle, so the drawer shows a real diff.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add ducklake schema support to the database manager
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: support schema in wmill.ducklake("name:schema") template helper
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: preserve schema when parsing ducklake asset/favorite paths
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts for ducklake schema syntax doc
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): wire DB-backed autosave into the whitelabel flow SDK
FlowWrapper (the @windmill-labs/components flow editor entry) was never
updated after DB-backed user drafts moved autosave wiring to the page
layer, so the SDK editor had no autosave and never rendered the
AutosaveIndicator. Back the bound store with a per-user UserDraft handle
(workspace-guarded so it no-ops before a workspace exists) and pass
liveEditorDraftStoragePath so the indicator and Ctrl/Cmd+S flush engage.
Also set $workspaceStore on the /test_dev/sdk_flow harness page, which
lives outside the (logged) layout and so had an empty workspace store
(mirrors the sibling sdk_resource page).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): shared test_dev header to log in + set the SDK token
Add a common TestDevHeader (rendered by a test_dev/+layout) that logs in
(email/password → bearer token), lets a token be pasted/set manually,
picks the workspace, loads the user, and persists the session across
reloads — mirroring the React SDK's initializeClients. test_dev routes
live outside the (logged) layout, so this is the single place that wires
OpenAPI.TOKEN + workspaceStore + userStore for the SDK demo pages.
Drop the now-redundant per-page workspace/user wiring from sdk_flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(frontend): reuse usePageDraftSync in the flow SDK instead of a parallel copy
FlowWrapper hand-rolled UserDraft.useMany + a manual seed effect, duplicating
the core of usePageDraftSync but dropping recordRemoteSync/seedBaseline/discardIf
— a divergence that would drift. The only reason it couldn't reuse the helper
was that useReactive passes workspace straight into useMany, whose reconcile
called resolveWorkspace() (which throws) before the detached-handle check.
Make reconcile resolve the workspace without throwing and treat an absent
workspace like an empty path — handing out a detached, local-only handle that
re-keys into a real entry once the workspace resolves. FlowWrapper then reuses
usePageDraftSync directly, keeping one code path for the page and SDK editors.
Seed via the spec's defaultValue (threaded through usePageDraftSync ->
useReactive -> useMany, captured once on first acquire and swallowed by the
syncer's seed guard) rather than a manual first-write effect, dropping the
fragile skipNextWrite assumption and the seededPath bookkeeping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): wire DB-backed autosave into the whitelabel script SDK
ScriptWrapper had the same gap FlowWrapper did: ScriptBuilder delegates its
draft handle to the page (it only stop/restart-syncs and flushes by
userDraftPath), so the SDK's plain `bind:script` never reached a UserDraft
handle — no autosave, no indicator. Back it with usePageDraftSync<script>
(bind:script={draftSync.draft}, userDraftPath), seeded from the consumer's
script via defaultValue. Same one-code-path reuse as the flow SDK.
AppWrapper needs no change: AppEditor already self-acquires its handle
(UserDraft.use('app', ...)), so apps autosave already — and now also tolerate
mounting before login via the reconcile change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): gate SDK editors on a resolved workspace
Before a workspace exists the draft handle is detached (local-only); editing
into it and then having the workspace resolve re-keys to a fresh real entry
seeded from the original value, silently dropping those edits. Gate the flow,
script, and app SDK editors on `$workspaceStore` so no editing happens until
the real draft key exists. Embedders set the workspace before rendering (React
SDK initializeClients); the test_dev header sets it on mount.
AppEditor additionally acquires its handle at init from a non-reactive
workspace, so gating AppWrapper also ensures it mounts with the workspace
already set rather than permanently detached.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): add sdk_app test_dev page for the app editor SDK
Exercises AppWrapper the same way sdk_flow/sdk_script exercise their editors,
under the shared TestDevHeader. Confirms the app editor's self-managed autosave
+ AutosaveIndicator work via the SDK wrapper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Points LATEST_GIT_SYNC_SCRIPT_PATH at the republished sync-script-to-git-repo
(windmill-labs/windmill-integrations#155) pinning windmill-cli@1.728.1, which
carries the gitSyncIncludePattern __mod/** fix (#9606). On-deploy git-sync was
running windmill-cli@1.713.2 and filtered workflow-as-code (WAC v2 / module)
scripts stored under <path>__mod/ out of the deploy pull, so they never reached
the repo.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: zero-setup oauth client credentials for registry-declared providers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: support client-credentials-only custom oauth providers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: add coupa client credentials provider to oauth registry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: clarify oauth resource connect auth-method selection
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: support shared instance-level oauth client credentials
Admins can designate an instance OAuth entry's credentials as client
credentials; the connect dialog then runs the exchange server-side with
them instead of asking each user for their own. Replaces the per-provider
"Support Client Credentials Flow" toggle with a grant-type selector.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: update ee-repo-ref to be9f23b2c06b8b6ee0cd3e4d9f16bcd9e90129fb
This commit updates the EE repository reference after PR #613 was merged in windmill-ee-private.
Previous ee-repo-ref: 05643cbbc8c1bebf3509c691c5811b4057d96485
New ee-repo-ref: be9f23b2c06b8b6ee0cd3e4d9f16bcd9e90129fb
Automated by sync-ee-ref workflow.
* feat: allow both grant types on an instance oauth entry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: bring-your-own oauth credentials from the others section
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: segmented oauth grant-type selector, always show grant
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: enable client credentials for 5 more oauth providers
Verified against official docs: bitbucket, linkedin, spotify, xero and
zoho support the standard client_credentials grant with a plain
client_id + client_secret, compatible with Windmill's token exchange.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: hide create-manually link on the managed oauth connect path
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: enable client credentials for salesforce and servicenow
Salesforce CC requires the org's My Domain token endpoint (login.salesforce.com
is unsupported for that grant), so add an optional cc_token_url registry field
that the connect form prefills for the client-credentials path instead of the
shared token_url. ServiceNow uses the same instance host for both grants, so it
only needs its token URL and req_body_auth surfaced at the top level.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: add instance-level client-credentials token url override
Some providers use a per-org/instance-specific token endpoint for the
client-credentials grant that differs from the authorization-code URL.
Add an optional cc_token_url on the instance OAuth entry, surfaced in
instance settings (prefilled from the registry template) when client
credentials is selected, and used for the CC exchange and refresh while
auth-code keeps its own token URL.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* style: remove redundant grant-type tags from oauth auth cards
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: extract reusable RadioCard component for the oauth auth chooser
A token-based selectable card (label, description, selected, onSelect,
optional icon) replacing the inline cards in the connect dialog.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: hide sign-in option on the bring-your-own oauth path
Picking a provider from "Others" means bring your own credentials, so
the auth-code "Sign in" card (which uses the instance client) no longer
shows there — it goes straight to the client-credentials form. The
two-flow chooser stays on the instance-configured path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: restrict client-credentials token url to caller-supplied creds
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: resolve client-credentials id and secret all-or-nothing
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: host-pin client-credentials token url via instance-name input
For registry providers whose CC token URL is instance-templated (Coupa,
Salesforce My Domain, ServiceNow), the connect dialog and instance settings
collect an instance name and the backend substitutes it into the fixed-host
template, validating it as a hostname label. A free-form token URL is no longer
accepted for these providers, so the exchange host cannot be redirected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: client-credentials token url always comes from the registry
Bring-your-own CC is registry-only: the token URL is resolved server-side from
the built-in registry (host-pinned via an instance name for templated providers,
the fixed registry URL otherwise) and rejected for custom resource types. The
caller-supplied token URL field is removed from the connect dialog and the API.
Adds unit tests for the resolver.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address CC review - sandbox CC config and instance-templated providers
Resolve `_sandbox` provider keys to the parent registry entry in the instance
settings and connect-dialog helpers, so salesforce_sandbox (and future sandbox
entries) can enable client credentials. Use the effective CC token URL template
(cc_token_url or token_url) so the instance-name field works for Coupa/ServiceNow,
and hide that field when a connect_config_template already owns the instance input
(ServiceNow). Document the authorization contract on resolve_instance_cc_credentials.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: unify instance-templated oauth onto connect_config_template
Remove the separate cc_token_url and cc_instance config fields. An instance-
templated provider now declares one connect_config_template (auth_url optional
for client-credentials-only providers like Coupa); the CC flow reads its token
URL, label and strip_suffix to host-pin the exchange. Coupa and ServiceNow move
to connect_config_template; Coupa stays drawer-only (no auth_url -> excluded from
instance settings). Salesforce CC is removed for now (its auth-code/CC host split
needs the endpoint-profiles model).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: cc_scopes defaults and instance config for client credentials
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: store empty auth_url for cc-only templated oauth providers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address review nits - sandbox key lookup, template doc, deref specs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: default shared client-credentials connect to cc_scopes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: support bring-your-own client credentials for instance-configured providers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: move oauth grant-type help into per-option tooltips
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: keep instance-configured oauth providers selectable from Others
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: preserve admin-configured scopes for custom client-credentials providers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: use cc scopes on cc refresh and enforce cc grant for bring-your-own
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: require {instance} in leftmost host label for cc token url templates
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: drop token_url from unauthenticated get_connect response
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: fill byo templated resource args from the entered instance
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 136f4634aca61e74ccb045372358a1e3f6b23e75
This commit updates the EE repository reference after PR #616 was merged in windmill-ee-private.
Previous ee-repo-ref: b5083e266492e908456e39401778a9cdcea46e94
New ee-repo-ref: 136f4634aca61e74ccb045372358a1e3f6b23e75
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Previously the AI sessions sidebar section was hidden entirely when AI was
not configured at the workspace level. Now the section stays visible and the
per-session chat input is disabled with an explanatory message, mirroring the
sidebar AI chat behavior.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(cli): improve generate-metadata guidance, fix description parser
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(cli): surface dependency version bumps after generate-metadata
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(cli): explain generate-metadata scope, import cascade, and --dry-run troubleshooting
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The localStorage→DB user-draft migration upserted via /drafts/update, whose
SQL always stamped created_at = now(). Every migrated draft therefore
resurfaced to the top as freshly created, regardless of its real age.
Add an optional created_at override to the update_draft request, threaded
into the upsert as COALESCE($8, now()) / created_at = EXCLUDED.created_at.
Normal saves omit it and still stamp now(); the migration passes the draft's
original write time (or epoch 0 when unknown) so migrated drafts keep their age.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three issues from the Codex PR review of the low-code app deploy + summary work:
- [P1] The summary mirror onto the autosaved App value broke the autosave's
no-op detection for deployed apps: `discardIf` compares the live value against
the deployed baseline, but the baseline (the deployed App value) carried no
summary while the live value now always does — so a draft reverted to the
deployed state never compared equal and a no-op draft was persisted instead of
deleted. Carry the deployed summary onto the baseline so the comparison matches
(a summary-only edit still counts as a real change).
- [P2] "Show diff" stayed enabled for view-only (`mine=false`) rows in the
"Show all drafts" view, but the diff only fetches the current user's draft
overlay — wrong diff for another user's deployed-row draft, 404 for their
draft-only row. Hide it for foreign rows; own/legacy rows keep it.
- [P2] Reword the `rawAppDraftValue` doc comment to state the current invariant
(must read a draft's top-level `files`) instead of referencing past drafting
history, per AGENTS.md.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: per-user draft gating, badges and rename display on deploy page
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): don't strike the path when a draft adds a summary to a summary-less item
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): don't strike draft-only items' auto-generated path against the pretty path
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): deploy raw-app drafts from top-level files so the bundle isn't dropped
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(frontend): share raw-app source→draft-value projection across chat and deploy page
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): deploy renamed/new flow, app and raw-app drafts at draft_path, not the temp storage path
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(frontend): add a design-system Checkbox and use it for deploy-page row/select-all checkboxes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: "Show all drafts" toggle on the deploy-drafts page
Replace the deploy-drafts page's legacy-hiding "Only my drafts" toggle with
a "Show all drafts" toggle that switches the listing scope between the
current user's own drafts (+ legacy no-owner rows) and every user's drafts
in the workspace.
Backend (`drafts.rs`, `openapi.yaml`):
- `/drafts/list` gains an `all_users` query param that drops the owner
filter, and a per-row `mine` flag (own draft or legacy no-owner row).
`DISTINCT ON` now prefers the user's own row, then the legacy row, then
another user's, so `mine`/`legacy_draft` describe the kept row.
Frontend (`CompareDrafts.svelte`, `workspaceDrafts.svelte.ts`):
- "Show all drafts" toggle (default off). The all-users superset is fetched
lazily via the shared resource only while the toggle is on, so the page's
fork draft-count (own drafts) is unaffected.
- Other users' drafts are view-only: disabled checkbox + Discard with a
"belongs to another user" tooltip; Show diff stays enabled. Selection,
select-all and the deploy count only ever include the user's own drafts.
The multi-user warning triangle shows on owned rows only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(backend): gate all_users draft listing by read permission
Addresses the PR review on the per-user deploy-drafts page:
- `/drafts/list?all_users=true` previously had only `WHERE workspace_id = $1`
with no read-permission check, so any non-operator could enumerate every
draft's path, summary and authors — including items they can't read. Now
rows the caller doesn't own (`mine = false`) are gated through
`require_can_read_path` (the same gate `/drafts/get` uses) and dropped when
unreadable; both its `NotFound` and `NotAuthorized` denials are treated as
"not visible".
- Skip the per-row `require_can_write_path` probe on those non-owned rows
(they're never selectable — `isSelectable` requires `mine`): set
`can_write = false` directly, removing a redundant N RLS write-probes when
`all_users` is on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): only confirm destructive draft discards on the deploy page
Discarding a draft is non-destructive in every case except removing the last
draft of a never-deployed item (`draft_only` with no other user's draft),
which permanently deletes it. Confirm only that case; reverting a draft over a
deployed item, or discarding your copy while another user still holds a draft,
now runs immediately (the ⚠️ already signals the multi-user case). Drops the
redundant "other users still have a draft" / "deployed version unaffected"
confirmation branches.
Harden the destructive check: it keyed off `otherDraftUsers()`, which subtracts
`currentUsername`; while `$userStore.username` is unhydrated, your own draft
looked like another user's, flipping a draft-only item to "non-destructive" and
deleting it with no confirmation. Now: deployed counterpart → never destructive;
`draft_only` with unknown `currentUsername` → treated as destructive (confirm).
The delete modal also shows the friendly `draft_path` instead of the raw
`draft_{uuid}` storage path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): deploy low-code app drafts (value + summary persistence)
A visual (low-code) app draft is autosaved as the *bare* App value
(grid/theme/... plus a draft-only `draft_path`), not wrapped in
{ value, summary, policy } like script/flow drafts. The Review & Deploy page
read `requestBody.value = d.value` — undefined for that shape — so deploying any
low-code app draft (created or edited) sent no value and failed. Read the value
from the draft object itself, strip the draft-only `draft_path` from it, and use
that as the deploy path.
Also persist the app summary, which was dropped entirely: the autosave stores
the bare App value (the summary normally lives only in the `app` table column,
set on deploy), so a draft never carried it — reopening a draft or deploying it
lost the summary. Mirror the summary onto the autosaved App (like `draft_path`),
read it back when loading a draft, and on deploy send it as the summary column
while stripping it (and `draft_path`) from the deployed value so the value stays
clean.
Verified end-to-end: a new low-code app with a summary deploys at its pretty
path with the summary set, content intact, and no draft_path/summary leaked into
the deployed value; the draft is cleaned up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): queue messages typed while ai chat is streaming
* fix(frontend): avoid losing queued chat messages on send early-return
* test(frontend): cover queued chat message semantics in AIChatManager
* fix(frontend): complete ChatLoopResult mock in queued message tests
* feat(frontend): single appendable queued message, send on cancel
* fix(frontend): only auto-send queued message on a user cancel, not programmatic
* chore(frontend): remove queued-message dev preview page
* fix(frontend): clear queued chat message on conversation switch
* fix(cli): fall back to esbuild-wasm on native host/binary mismatch
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): guard tarball extraction, extend esbuild-wasm fallback to script bundling
Address CI review: prevent tar-slip in esbuild-wasm package extraction, route codebase/script and inline-rawscript bundling through getEsbuild() too, and move the loader to utils. Add a unit test for the tar-slip guard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): make esbuild-wasm fallback concurrency-safe
Address CI review (P1): memoize getEsbuild() on an in-flight promise so concurrent first callers (parallel wmill sync push) share one probe/download instead of racing, and give each extraction a unique temp dir so concurrent extractions can't clobber each other.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(backend): purge workspace_diff cache on workspace delete
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(backend): add sqlx cache for workspace_diff regression test queries
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(backend): clear stale fork diff state on fork creation and backfill
Purge inherited workspace_diff/skip_workspace_diff_tally rows when a fork is
created (reused ids would otherwise leak a prior occupant's cached diff state),
and extend the cleanup migration to drop live-pointing stale skip rows that
short-circuit compare_workspaces before the has_changes reset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): persist raw-app draft path edits in the session editor
Renaming a raw app's path in the session preview editor never triggered a draft
save: the header surfaced the typed path as `pendingDraftPath`, but
RawAppEditorView ignored it (it never reached runtime.rawApp.val), and the
RawAppDraft codec didn't serialize a path field — so the autosave signature
(JSON.stringify(draft)) was unchanged and nothing was written. The rename was
lost and the home/review/Drafts lists kept the original `draft_path`.
- appDraftCodec: make `draft_path` a real draft + runtime field, serialized by
runtimeRawAppToDraft and round-tripped by applyDraftToRuntimeRawApp, so a path
change moves the sig and fires a save.
- sessionRuntime.loadRawApp (+ inline rawApp.val type): seed `draft_path` from
the loaded draft so it survives reloads.
- RawAppEditorView: bind the header's `pendingDraftPath`, mirror it into
runtime.rawApp.val.draft_path (guarded so the initial undefined can't clobber
the seed or fire a spurious save), and seed the path widget from
`draft_path ?? path`. Mirrors the full-page /apps_raw/edit route.
- appDraftCodec.test: add a draft_path round-trip test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): per-line tooltips in workspace item diff rows
The summary line now shows the full summary on hover and the path line the
full path, instead of one row-level title surfacing the path everywhere.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): persist flow/script draft path edits in the session editor
The session sync dedups on a per-kind signature that omitted the path, so a
rename never moved the signature and never autosaved. Add path/draft_path to
the flow signature, and derive draft_path in the script codec (scripts bind
the Path widget to script.path directly) so the rename both autosaves and
shows the typed name in the home/Drafts lists. Mirrors the raw-app fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): commit editor summary edits live instead of on blur
EditableInput only fired onSave on Enter/blur, so the summary in the shared
editor header only updated when the field lost focus. Add an opt-in
commitOnInput that fires onSave per keystroke and enable it for the header
summary, so flow/script/raw-app summaries autosave as you type.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): preserve renamed never-deployed script path on session re-seed
loadScript seeded a draft-only script's baseline path from the storage key, so
re-running it with the draft still in memory (e.g. a script→script switch)
reset the path to draft_<uuid> and the next autosave dropped draft_path,
clobbering the rename. Seed from the draft's own draft_path/path instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): clear raw-app draft_path when the path rename is reverted
The mirror effect only ever set draft_path, so reverting/clearing the path
field left a stale friendly name in the draft (persisted by the codec and shown
in the home/Drafts lists). Track whether a real typed path was surfaced so a
revert clears draft_path while the initial pre-bind undefined still can't
clobber the loadRawApp-seeded value. Mirrors the script codec's drop-on-revert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface folder labels in the /folders table via a new "Labels" column
between Name and Scripts, rendered as blue badges with a +N overflow
indicator (first 3 shown), matching the script row pattern. Previously
labels were only visible inside the folder editor drawer.
Fixes WIN-2056
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): session Drafts drawer uses raw_app kind for the raw-app diff
Follow-up to #9601. DraftDiffDrawer mapped a raw_app row back to `app` before
calling getDraftDiffValues(), but that helper sends `rawApp:true` only for the
exact kind `raw_app` (which a never-deployed raw app needs). With `app` it hit
the normal app endpoint and 404'd instead of rendering the added diff. `raw_app`
isn't in the deploy-kind maps anyway, so just pass the row kind through.
Caught by the Codex auto-review on #9601, which posted after that PR had already
merged (locked conversation), so the fix lands separately here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): show friendly draft path + summary for all kinds in session Drafts drawer
A never-deployed app/raw_app is parked at a synthetic `u/.../draft_<uuid>`
storage path with the user's typed name in the draft JSON's `draft_path`; the
Drafts drawer rendered that UUID path. The list endpoint already returns
`draft_path` and `summary` for every kind, but `fetchDrafts` dropped them and
the drawer only had the lazily-derived summary.
Thread both through the shared row: `WorkspaceDiffDrawer` gains optional
`displayPath` (shown in tree/header/search, while `path` stays the storage key
for value-loading, item keys and edit links) and `summary` (preferred over the
value-derived one, shown before the diff loads). `DraftDiffDrawer` populates
them from the draft list (`draft_path ?? path`, `summary`).
Both fields are opt-in via `?? path` / lazy fallback, so ForkDiffDrawer — the
other consumer of the component — is unchanged. The symptom only surfaced for
apps/raw apps because their storage path diverges from the friendly name;
scripts already kept a readable path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): use friendly display path for single-segment draft tree nodes
buildTree splits displayPathOf(d), but the `< 2 parts` branch still named the
file node from the storage `path` — a draft whose friendly path is a bare name
(no `/`) would show `…/draft_<uuid>` in the sidebar tree. Name it from
displayPathOf(d) too, consistent with the rest of the tree/header/search.
Addresses Codex and claude review nits on #9617.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The on:confirmed handler for the delete-fork ConfirmationModal never reset
deleteWorkspaceForkModal to false. Since SidebarContent persists across
workspace switches, the stale true state caused the delete-fork modal to
immediately reappear when a new fork workspace was created. Reset the state
before calling deleteFork(), matching the on:canceled handler and the pattern
in forks/compare/+page.svelte and SessionWrapper.svelte.
Fixes WIN-2057
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): adapt AI-chat/sessions drafts to DB-backed model
PR #9351 dropped UserDraft's localStorage layer; the chat adapter's
synchronous save->read-back threw "Could not read written draft". The
adapter now treats the backend as source of truth (in-tab cell used
opportunistically for live-preview coherence) with conflict-on-save,
and read tools fall back to the backend. Collapses the six writeXDraft
functions onto one generic writeDraft + typed per-kind WriteSpec
constants. Terminology: "local draft" -> "draft" (drafts are server-side).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): autosave indicator + draft-only diff guard in session editors
Thread an explicit (workspace, path) autosave target to the cloud
AutosaveIndicator in the Script/Flow/RawApp session previews so it
watches the same key saves land on (it previously watched an empty path
and never animated). Disable the Diff button with a hint for draft-only
(no_deployed) items consistently across the three editors. Adjust the
script topbar compact breakpoint/layout so the cloud icon is part of the
bar, and stop splitpanes over-constraining session panes on reload.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): session draft diff viewer for schedule/resource/variable
Canonicalize both sides of the draft diff onto one field set and strip
runtime-only fields so rows aren't spuriously marked all-changed; mask
secret values. Map draft itemKinds to deploy-style kinds so the DiffRow
shows the correct icon/label.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): uniform diff-viewer row height regardless of summary
Diff-viewer leaf rows (WorkspaceItemRow) drew two lines when an item had a
summary and one line otherwise, giving unequal heights. Add an opt-in
`uniformHeight` prop that gives the text wrapper a shared min-height and
vertically centers the one-line case; enable it only from the diff viewer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(frontend): address review nits on the drafts diff/guard changes
- Reuse the exported TRIGGER_RUNTIME_IGNORE from utils_deployable instead of
a verbatim copy, so the runtime-field ignore list has one source of truth.
- Drop the now-redundant `(savedApp as any)` cast in RawAppEditorHeader; the
prop type already carries `no_deployed`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): add description parameter to the write_flow chat tool
write_flow had no way to set a flow's top-level description (the sibling
of summary in OpenFlow); patch_flow_json only edits the compact value, so
the field was unreachable from the AI chat. Thread an optional description
end-to-end: tool schema -> persisted draft -> read-back -> deploy body.
Structural patches (patch_flow_json/set_flow_module_code) pass no
description, so a previously-set description is preserved. Adds a
deployRequests regression test asserting a draft description reaches the
deploy body, overriding the deployed one.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): round-trip top-level fields in session preview draft sync
The session preview's two-way draft sync dedups on a per-kind signature
and mirrors fields between the editor store and the shared UserDraft cell.
Both omitted fields the chat can set, so with the preview open a change to
only that field was swallowed (identical signature) and then clobbered by
the editor's outbound save:
- flow: the signature and applyDraftToStore ignored top-level `description`.
- script: the signature keyed on `content` alone, dropping `summary`/`language`.
Add the missing fields to flowDraftSig and the script codec signature, and
copy `description` in the flow codec's applyDraftToStore (mirroring `summary`).
Raw-app already stringifies the whole draft, so it was unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): deploy draft-only flow from the session preview
Deploying a draft-only flow (a draft with no deployed row) from the session
preview hit two gaps the full-page flow editor already handled:
- create vs update: newFlow keyed on `!savedFlow.val`, but a draft-only flow
has a synthesized savedFlow (no_deployed=true), so deploy took updateFlow
against the draft path and 404'd "Flow not found". Key it on no_deployed too.
- friendly name: a brand-new flow is stored under a `draft_<uuid>` path with
its intended name in `draft_path`. Seed the builder's initialPath from
`draft_path` (as the full-page editor does) so the Path widget and deploy
use the friendly name instead of creating a flow named draft_<uuid>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): deploy draft-only raw app from the session preview
Same create-vs-update bug as the flow session preview: newApp keyed on
`!savedRawApp.val`, but a draft-only app has a truthy synthesized savedApp
(getAppByPath with rawApp:true resolves to the draft kind instead of 404ing,
carrying no_deployed=true), so deploy took updateApp against a path with no
deployed row and 404'd "not found". Key newApp on no_deployed too so a
never-deployed app deploys via createApp. More reachable than the flow case:
it hit any never-deployed app, including chat-created ones at friendly paths.
Keying newApp on no_deployed also exposed that newEditedPath (the breadcrumb
path AND the createApp target) used newApp to mean "brand-new, generate a
random name". A draft-only app is newApp=true but already has a real path
(empty newPath at init, but appPath is set), so it showed and would deploy a
random `*_app` name. Prefer the real appPath before the random fallback, so
only a genuinely new app (appPath === '') still gets a generated suggestion;
the full-page editor is unaffected (it always sets newPath).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): don't re-save a draft after deploying from the session preview
Deploying from a session preview reloaded the editor (expected) but then
immediately POSTed a fresh draft. The full-page editor guards deploy with
discardDraftAfterDeploy (stopSync + arm-restart-on-first-interaction), but the
shared editor header skips that in a session pane (inSessionPane) and routes
post-deploy cleanup through sessionRuntime.syncPreviewWithDeployed, which did
discard + reload without the stopSync guard. UserDraft.discard keeps the cell
entry, so the reload's UserDraft.save fired the cell's reactive effect and
re-POSTed the just-deployed value as a draft.
Wrap the discard + reload in the same UserDraft.stopSync + armRestartOnFirst-
Interaction bracket. One place fixes all three kinds (script/flow/raw_app),
since they all funnel through syncPreviewWithDeployed; autosave resumes on the
next genuine edit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(frontend): address review findings on the session-preview drafts work
- Type `no_deployed` via the GetXByPathResponse/UserDraftOverlay types instead
of `(result as any)`/`(saved as any)` casts at the sites this branch added
(sessionRuntime, ScriptBuilder, FlowBuilder, + widened the script/flow
builder prop types). Pre-existing trigger/variable/resource-editor casts
left untouched.
- Drop a history-narrating comment parenthetical per the AGENTS.md comment
policy (RawAppEditorView).
- Add a unit test covering persistGlobalDraft's conflict-on-save / override
path (conflict-capable updateDraft mock; inert for existing tests).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): keep the friendly generated path for a brand-new raw app
The earlier draft-only newApp fix made newEditedPath prefer `appPath` before
the random suggestion, but a brand-new app is parked at the storage placeholder
`u/{user}/draft_{uuid}` (the /apps_raw/add redirect target), so it surfaced that
uuid instead of a friendly `<adjective>_app` suggestion. Reject a `draft_`
placeholder segment when choosing the path: a real named/draft-only path is
still kept, a placeholder falls through to the generated suggestion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): show the Diff-button tooltip when it's disabled
A disabled <button> fires no pointer events and browsers suppress its
native title, so the "deploy once to compare" explanation never showed on
hover for a draft-only item's Diff button. Wrap the button in a titled
element and set pointer-events-none on the button when disabled, so the
hover reaches the wrapper. Applied in ScriptBuilder, FlowBuilder, and
RawAppEditorHeader (covers both the full-page editors and the session preview).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): surface draft save failures and conflicts in the AI chat tools
Addresses Codex + Pi review findings on PR #9601 (three P1s, all in the
DB-backed draft adapter reporting success when the write didn't land):
- persistGlobalDraft reported {status:'saved'} even when UserDraftDbSyncer.save
failed (it records network/5xx into a failure map instead of throwing). Check
getState().state==='failed' after the save and return a new 'error' status;
finishDraftWrite now emits success:false with a retry hint.
- saveGlobalAppDraft dropped the conflict/error status (returned only the item),
so write_app_file/patch_app_file/write_app_runnable reported every stale or
failed write as saved. It now returns the full DraftPersistResult, and the six
app write tools route through a shared finishAppDraftWrite helper.
- fetchBackendDraftValue's catch{} swallowed non-404 errors (403/500/network),
collapsing them to "no draft" so the write merged from the deployed item and
lost in-progress draft edits. Narrow the catch to status===404; propagate the
rest.
Adds unit coverage: save-failure -> 'error', non-404 read -> propagates,
raw-app stale write -> 'conflict'. 71/71 pass, check:fast + full build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): surface failed draft deletes + strip tool-only override from schedule drafts
Addresses Codex's second-round review on PR #9601:
- [P1] deleteGlobalDraft reported success even when the server delete failed or
conflicted (UserDraftDbSyncer.save records failure state instead of throwing) —
so discard_local_draft / deploy_workspace_item / delete_workspace_item / the
/global_drafts delete could report a draft removed while the DB still had it.
Check getState().state and getConflict() after the awaited null save and throw,
mirroring the write-path guard.
- [P2] writeScheduleDraft persisted the tool-only `override` conflict flag into
the schedule draft value (mergeDraftConfig cloned every arg field). Strip
`override` in SCHEDULE_SPEC.buildDraft before merging.
Tests: failed server delete -> throws; schedule draft no longer contains
`override`. 73/73 pass, check:fast + full build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): /global_drafts "Clear all" deletes persisted drafts, not just cells
Codex review nit (P2): the dev-only global-drafts inspector's "Clear all" called
clearGlobalDrafts(), which only iterates in-tab UserDraft cells — any persisted
backend draft row not currently mounted as a cell survived, so the list re-showed
it after refresh. Iterate the listed drafts and delete each via the backend-aware
deleteGlobalDraft() (continue past per-row failures), matching the per-row delete,
then clear local cells + ephemeral secrets.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(nativets): respect custom CA certs in in-process fetch runtime
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(nativets): dedupe CA file paths and clarify DENO_TLS_CA_STORE semantics
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(nativets): resolve CA env vars from worker-group config too
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When editing a resource whose type definition does not exist in the
workspace (e.g. custom types not yet synced), the JSON fallback editor
rendered empty. The pre-refactor ResourceEditor seeded rawCode from the
resource args in its loadResourceType() catch block; the new
ResourceForm only populated rawCode when the user toggled viewJsonSchema.
Add a reactive effect that seeds rawCode from args when the resource
type schema is unavailable, restoring the old behavior so the resource
data is visible in the JSON editor.
Fixes WIN-2045
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
A draft-only app (one that exists only in the `draft` table and was never
deployed) failed to load when opened from the home list: the row linked to
the viewer `/get/` route, whose `get_app_lite` backend handler 404s when
there is no deployed version.
Route `draft_only` apps to the `/edit/` route instead, matching the
existing behavior in ScriptRow and FlowRow. Covers both raw and regular
draft-only apps.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Add a variable" drawer (and other editors built on `UserDraft.useMany`)
opened empty: for a brand-new item `editPath` is undefined so the spec path is
empty, which routes through `useMany`'s empty-path branch. That branch handed
out a `makeDetachedHandle()` whose cell was initialized to `undefined`,
ignoring the spec's `defaultValue`. The editor binds its form behind
`{#if current}` where `current = states[ws]?.draft`, so an undefined cell left
the drawer with just the title and a Save button.
Seed the detached handle with `defaultValue`, and re-seed it when the caller
supplies a fresh `defaultValue` reference (reopening the drawer clones a new
default) so a reopened editor starts clean instead of replaying the previous
session's edits — the reference is stable within a session, so live edits are
never clobbered. Also drop detached handles that fall out of the specs so they
don't leak.
Regression from #9351 (db-backed user drafts).
Fixes WIN-2054
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-hosted instances that embed their own UI (Windmill as backend, custom
app at the instance root) can't be redirected back to their app after OAuth
login: isValidLogoutRedirect rejects same-origin absolute URLs, so the login
callback falls back to a client-side goto('/') into Windmill's own dashboard.
Same-origin redirects are never open redirects (and toSameOriginRelativePath
already treats them as safe), so accept them.
Also make the test-setup window global configurable so vitest's stubGlobal
can redefine it across the window-stubbing test suites.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(cli): include __mod/ folder in gitSyncIncludePattern for scripts
Scripts with companion modules use a `__mod/` folder layout on disk
(`path__mod/script.ts`, `path__mod/script.yaml`, ...). The default case of
`gitSyncIncludePattern` returned only `${path}.*`, which does not match files
inside `__mod/`. During git-sync deployment the `extraIncludes` filter then
excluded all module files from the pull, and the subsequent
`git add '${path}**'` failed with "pathspec did not match any files" because
nothing was written to disk.
Add the `${path}__mod/**` pattern so module files are pulled, mirroring the
existing dual-layout handling for flows (`.flow/*,__flow/*`) and apps.
Fixes WIN-2052
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(cli): e2e guard that module scripts' __mod/ files land on git-sync deploy branch
Add a promotion test mirroring the existing trigger/schedule cases: deploy a
script WITH companion modules (one flat, one nested) under use_individual_branch
and assert the `__mod/` entry point and module files land on the wm_deploy
branch. Without the gitSyncIncludePattern `__mod/**` fix the extra-includes
filter matches none of those files and the branch is created without them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: support temp_script_refs in wmill dev for local relative imports
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: add unit tests for getAllTempScriptRefs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): strip server-managed fields from value diffs
The script editor's "Deployed <> Current" diff rendered spurious metadata
changes (created_at, created_by, extra_perms, lock_error_logs, ...) the user
never touched. Since #9351 the edit loader fetches the full Script row via
getScriptByPath (instead of the trimmed NewScript-shaped getScriptByPathWithDraft),
so the editing object carries these DB-managed fields. The deployed side is
trimmed in syncWithDeployed, so the two sides no longer match.
Normalize both sides at the shared chokepoint: cleanValueProperties now also
strips created_at, created_by, extra_perms, workspace_id, parent_hashes, lock
and lock_error_logs. These are never user-editable, so this also fixes the
draft<>current diff and unsaved-change detection, and benefits the flow/app
diff viewers that share the helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(frontend): cover cleanValueProperties; keep lock in diffs
Add unit tests for cleanValueProperties asserting the server-managed
bookkeeping keys are stripped while user-editable keys survive.
Keep `lock` out of the stripped set: it was part of the value comparison
before the full-DB-row loader (#9351) and version-to-version diff viewers
(WorkspaceItemDiffViewer) legitimately surface lockfile changes. Only the
fields that the full Script row newly introduced as diff noise are stripped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): keep extra_perms in shared diff, strip script noise at source
Address review: stripping `extra_perms` in the shared `cleanValueProperties`
hid folder sharing-permission changes in workspace/fork diffs (the backend's
compare_two_folders treats folder extra_perms as a real change). Remove it from
the global skip-list so folder diffs surface it again.
The script-editor noise it (and `lock`) would otherwise cause is now stripped at
the source instead: `ScriptBuilder.openDiffDrawer` nulls `lock`/`extra_perms` on
the current side to match the existing deployed-side strip in `syncWithDeployed`.
Also strip the draft-overlay bookkeeping fields the full DB row carries
(`draft_saved_at`, `draft_created_at`, `is_draft`, `other_drafts_users`) — they
leaked into the current side and showed as spurious metadata diffs.
Verified in browser: no-edit diff shows "No changes detected"; a summary edit
surfaces only that change, with no lock/extra_perms/draft metadata noise.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NodeWrapper destructured `moveManager` from `getGraphContext()` unconditionally,
but FlowGraphContext is only set by the flow graph. The app decision-tree editor
reuses NodeWrapper without setting that context, so opening its Graph Editor threw
"Cannot destructure property 'moveManager' of getGraphContext(...) as it is undefined".
Guard the context with `?? {}` since `moveManager` is already used optionally.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): session-pane draft seeding + restore actions
Seed per-tab last_sync from the server draft's draft_saved_at in the
loadFlow/loadScript "no local draft" branches (mirroring loadRawApp) so the
seeding save attaches a matching last_sync and the server no longer clobbers
an existing server draft with a fresh created_at.
Replace the no-op loadFlow/loadRawApp-based diff-drawer restore handlers with
proper restoreDeployed/restoreDraft that reset the live UserDraft cell (the
inbound sync then updates the preview) and delete the per-user server draft,
mirroring ScriptEditorView. Add rawAppValueToDraft to project a deployed
raw-app value into the draft shape.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(api): move UserDraftOverlay/UserDraftItemKind out of openflow inline block
These two schemas were defined between the python-client's
"# -- INLINE START/END --" markers, whose contents build.sh replaces with the
openflow legacy wildcard $ref. That deleted both definitions during bundling
while ~19 path responses still referenced them, failing the python-client
build. Relocated them after the marker block.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): explain legacy drafts in the draft badge popover
The home-page draft badge lists each draft owner; a workspace-level row from
before the per-user drafts migration shows as "Legacy workspace draft". Add an
info tooltip next to it explaining that a legacy draft isn't tied to any user
(email NULL) so everyone with access to the path sees it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(compare): show friendly draft path on the review & deploy page
list_drafts now surfaces the draft JSON's `draft_path` (when set and different
from the storage path) alongside summary, mirroring the home-page list
endpoints. CompareDrafts displays it instead of the `u/{user}/draft_{uuid}`
storage path, while all fetch/deploy/discard calls keep using the storage path
(the draft's server-side key).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(compare): delete the storage-path draft when deploying a renamed draft
Deploying a draft from the review page replays the editor's create/update at
the draft's friendly path, which deletes the draft server-side only at that
path. A never-deployed item parked at `u/{user}/draft_{uuid}` therefore left
its storage-path draft behind on deploy and kept listing. Delete the
storage-path draft for every kind after a successful deploy, mirroring the
editors' discardDraftAfterDeploy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(compare): badge legacy drafts on the review & deploy page
list_drafts now reports `legacy_draft` (true when the listed row is a
workspace-level NULL-email draft and no per-user row exists at the path).
CompareDrafts shows a "Legacy draft" badge with a hover tooltip explaining
these predate the per-user drafts migration and aren't tied to a user.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(compare): allow discarding a legacy draft from the review page
Legacy drafts (workspace-level, email NULL) aren't owned by the authed user,
so the email-scoped draft delete in update_draft never matched them and the
discard was a silent no-op. Add a delete-only `legacy` flag that retargets the
DELETE (and the conflict re-read) to the NULL-email row, and route the review
page's discard of a legacy draft through it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(backend): prune orphaned sqlx offline cache entries
Re-ran the canonical update_sqlx.sh after rebasing windmill-ee-private onto
origin/main and re-running substitute_ee_code.sh. Compiling the full workspace
with all features recorded every live query and pruned 55 stale cache entries
no longer produced by any query (22 are the removed `draft_only`-on-app
lookups dropped by the db-backed user drafts work; the rest pre-existing
orphans). Orphan entries don't break offline builds — this is cleanup only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(drafts): stop migrated draft-only items flooding the home list
20260609165313_remove_draft_only inserted the legacy (email IS NULL) draft
stubs without an explicit created_at, so every row defaulted to the migration's
now() (transaction_timestamp, constant for the whole transaction) and they all
bunched at the migration instant — flooding the top of the newest-first home
list.
Add a corrective migration that resets those rows' created_at to the epoch so
they sort to the bottom (their real per-item timestamps are unrecoverable —
the source rows were deleted and the draft value carries no timestamp; editing
one bumps created_at to now() and floats it back up). The rows are identified
exactly via _sqlx_migrations.installed_on, which sqlx writes in the same
transaction as the migration so it is byte-identical to the inserted rows'
created_at; rows edited since no longer match and are left alone. Leaving
remove_draft_only intact (rather than neutralizing it) keeps its essential
schema work running everywhere; this migration runs right after and corrects
the timestamps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(migration): note both timestamps are timestamptz in draft created_at repair
Pre-empt a misread: draft.created_at became TIMESTAMPTZ in
20260514233244, so `created_at = installed_on` is an exact instant comparison,
not a tz-sensitive timestamp/timestamptz cast.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(compare): resolve friendly draft path per kind + strip email from u/ path
list_drafts read the friendly path only from value->>'draft_path', which is
empty for scripts — the script editor binds the Path widget to script.path, so
the typed path round-trips through the draft JSON's own `path` (flows/apps/raw
-apps use draft_path). Read the right field per kind, matching the home-page
list endpoints, so renamed never-deployed scripts show their friendly name.
Also truncate the user segment at `@` when displaying a `u/{user}/…` path:
auto-generated draft slots are `u/{user}/draft_{uuid}`, and in the admins
workspace (or email-as-username setups) `{user}` is the full email
(`u/admin@windmill.dev/…` → `u/admin/…`). Display only — the path/key used for
fetch/deploy/discard is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(raw-app): make diff-drawer "restore to deployed" reset like the autosave indicator
The diff drawer's restoreDeployed ran the same runResetToDeployed as the
AutosaveIndicator's "Reset to deployed", but its onResetToDeployed callback
also did `redraw++`, remounting RawAppEditor mid-reset (inside the stopSync
bracket); the fresh mount's draft write resurrected the draft, so the restore
appeared to do nothing. Extract a single `reloadDeployed` callback (drop the
draft handle + reload without the draft overlay) and use it for the diff
drawer, the conflict modal, and the AutosaveIndicator so all three reset the
same way.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(compare): don't show auto-generated draft path as the bold title
A never-named draft lives at a synthetic `u/{user}/draft_{uuid}` slot. When it
had no summary and no friendly draft path, that uuid showed as the row's bold
title. Return '' from displayPath for auto-generated paths so they aren't
bolded — the row still shows the storage path in its secondary (grey) line.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(diff-drawer): remove obsolete draft-vs-current tab selector
The "Latest saved draft <> Current" comparison is obsolete. Remove the whole
diff-type tab selector; normal-mode diffs now always show deployed-vs-current,
simple-mode shows its single custom diff. Drop the now-unreachable
restore-to-draft button and the `restoreDraft` prop (plus the dead handlers in
the session editor views). The content/metadata selector is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(compare): make "Reset to deployed" work from the diff drawer
Route the raw-app session preview and the low-code app editor diff-drawer
restore through the same reset-to-deployed callback the AutosaveIndicator uses.
- Raw-app session: add a deployedOnly path to loadRawApp that bypasses the
draft (cell + server overlay) and reloads the deployed value; the diff
drawer's restore now runs it via runResetToDeployed instead of rebuilding the
draft shape in place (which hung and never reset). Also wires the in-session
AutosaveIndicator reset.
- Low-code app editor: drop the goto in the diff-drawer restoreDeployed that
re-ran the page load with the draft overlay on and resurrected the draft;
share one reloadDeployed across the diff drawer, AutosaveIndicator and the
load-latest-deploy modal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Archiving a workspace sets `deleted = true`, hiding it from the workspace
switcher for everyone (the `user_workspaces` query filters
`workspace.deleted = false`). The archive/delete actions were audited only
inside that same workspace's audit log, which then becomes inaccessible — so
there was no durable, discoverable record of who archived or deleted a
workspace, or when.
Also write these lifecycle events under the instance-level `admins`
workspace, the canonical instance-audit scope (a superadmin querying `admins`
with `all_workspaces=true` sees entries across all workspaces). The target
workspace id is carried in the audit `resource` field and the actor in the
author. For delete, the per-workspace rows are removed in the same
transaction, so the instance-level entry is the sole durable record.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Caching the gte-small embedding model fetched config.json / tokenizer.json /
model.safetensors from HuggingFace with no retry, so a single transient
network error ("error sending request for url ...") failed the whole image
build. Wrap each download in a retry loop (up to 5 attempts, exponential
backoff capped at 8s) that logs each retry and surfaces the error only after
the final attempt. No new dependency.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three independent failures on the latest release commit:
- pypi (Publish python-client): the `UserDraftOverlay`/`UserDraftItemKind`
schema definitions were placed inside the `# -- INLINE START/END --`
markers in openapi.yaml. The python-client build replaces that whole block
with a wildcard import of `openflow.openapi.yaml`'s schemas, which do not
define these two, so every `$ref` to them became unresolvable and the
redocly bundle aborted. Move both definitions outside the markers — they are
windmill-api schemas, not openflow-mirrored ones.
- flows::tests::flowmodule_serde: the expected JSON still carried
`"error_message": null` in three `stop_after_if` blocks, but
StopAfterIf.error_message is now skipped when None. Drop those keys.
- CLI Tests (test-windows): preservePendingScriptLocks mixed the OS path
separator (SEP) into map keys that are always forward-slash normalized,
so on Windows the multi-module suffix match and the lock-file lookup both
failed. Use forward slashes consistently; this also fixes real Windows
git-sync deploys, not just the test.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): add user-level toggle to disable Windmill AI
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): hide AI sessions sidebar section when AI is disabled
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): preserve committed script.lock on transient NULL lock during git-sync deploy (#9588)
A script's `lock` is NULL on the server only while a relock is mid-flight
(an importer relock after a relative-import dependency changed, or the
script's own first lock job). The git-sync deploy mirror reads the
workspace inside that window, sees no lock, and mirrors the transient
NULL as a deletion of the committed `.script.lock` plus a strip of the
`lock: '!inline …'` line — corrupting the git mirror until the relock
writes the identical lock back seconds later.
When pulling (remote -> local), carry the local committed lock onto the
remote map when the remote lock is NULL, so the diff is a no-op for both
the lock file and the metadata line. An empty-string lock ('') — the real
"no dependencies" state — is left untouched, so genuine lock removals
still propagate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): cover __mod multi-module scripts in pending-lock preservation
Address auto-review on #9593: the lock-file key was reconstructed from the
metadata path (`.script.yaml` -> `.script.lock`), so a multi-module script
whose lock lives at `…__mod/script.lock` fell through unprotected. Derive
the key from the committed `!inline` reference instead (covers both the
dotted and `__mod` folder layouts) and detect the folder-layout metadata
file. The reference is always forward-slash; convert to the OS separator so
the local map lookup matches on Windows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A flow module with a constant-only retry is stored by the frontend as
`{ constant: {...} }`, but round-tripping through the `Retry` struct (e.g.
the dependency/lock job, which re-serialises the flow value) materialised a
full default `exponential` block (`seconds: 0`, `random_factor: null`) and a
`null` `error_message`, because those fields are non-`Option` / `Option`
without `skip_serializing_if`. The defaults then got baked into stored data,
surfaced on `wmill pull`, and were rejected by the linter.
Skip serialising `Retry.constant`/`Retry.exponential` when they equal their
default, and `StopAfterIf.error_message` when it is `None`. Deserialisation is
unchanged (`#[serde(default)]` refills the in-memory structs), so the worker
retry logic and the frontend (which already optional-chains these fields) are
unaffected.
Verified end-to-end against a running backend: a flow created with an explicit
default exponential block + `error_message: null` comes back clean after its
lock job re-serialises it.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(security): sanitize dependency names & connection strings against command/SQL injection
Follow-up to the PowerShell module-name injection fix (#9587, CWE-78): the
same audit surfaced the identical "secondary identifier interpolated into an
interpreter/SQL command without escaping" pattern in a few other executors.
- R executor (the real twin, HIGH): package name/version parsed from a
user-supplied renv.lock were interpolated raw into an `Rscript -e
"...renv::install(\"{pkg}@{version}\"...)"` eval string. A double-quote in
the name/version broke out → arbitrary R (unsandboxed under DISABLE_NSJAIL /
non-Linux). Now validated in parse_renv_lock (charset) and escaped at the
sink as defense-in-depth (also escapes the lib path, which holds backslashes
on Windows).
- DuckDB ATTACH (MED): the connection string built from resource fields
(host/db/user/password) is embedded in a single-quoted DuckDB literal; escape
quotes so a field value can't break out of the ATTACH statement.
- PgDatabase::to_uri: URL-encode host and dbname (user/password already were),
so '@'/'/'/'?'/'&' can't reshape the parsed URI (feeds live PG connect and
DuckDB ATTACH).
- DuckDB CREATE SECRET (FFI): wrap the interpolated S3 key/secret/endpoint in
the existing sql_single_quote() helper, consistent with the resource-limits
setup right above it.
- PowerShell: also escape the configured private repo URL/PAT in the install
template (same sink as the module names; the escape landed after #9587 was
squash-merged so it was not in the merged change).
Adds unit tests for the R validation/escaping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(security): escape ducklake/snowflake/bigquery identifiers; preserve IPv6 host
- to_uri: don't percent-encode bracketed IPv6 literal hosts ([::1]) — encoding
their brackets/colons would stop them parsing as a host (review fix).
- duckdb ducklake ATTACH: the catalog conn string, storage and data_path are
embedded in single-quoted DuckDB literals; escape quotes so a resource field
can't break out (the ducklake path bypassed the ATTACH escape added earlier).
- snowflake: validate account_identifier (it forms the request hostname).
- bigquery: validate project_id (it forms a request URL path segment).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: remove deprecated enable_1m_context from AI provider code
1M context is now standard on Anthropic models — the beta header
`anthropic-beta: context-1m-2025-08-07` is no longer needed.
Remove the field from ProviderCredentials and AnthropicQueryBuilder,
and stop injecting the beta header in both the API proxy and worker
query builder paths.
The field is kept (as `_enable_1m_context`) on the ProviderResource
deserialization structs in both windmill-api and windmill-ai so
existing resources with the field still deserialize without error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: drop vestigial _enable_1m_context field from AI resources
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: assert legacy enable_1m_context keys still deserialize
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The list_apps and list_search_apps endpoints did not filter returned
rows against the calling token's resource-qualified scope. A token
scoped to apps:read:u/foo/specific_app could list every app in the
workspace, including full app_version.value definitions.
Apply build_scope_path_predicate, mirroring the protection already in
place for script, flow, resource and variable list endpoints.
Fixes WIN-2046
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The list_tokens handlers in scripts.rs and flows.rs accepted only the raw
DB pool, with no ApiAuthed extraction or check_scopes call. Any authenticated
token for the workspace — regardless of its scope restrictions — could
enumerate token metadata (label, prefix, scopes, owner email, timestamps)
for any script or flow path, bypassing the path-scoped read checks enforced
by sibling endpoints like get_script_by_path and get_flow_by_path.
Both handlers now extract ApiAuthed and call check_scopes for
scripts:read:<path> / flows:read:<path> before querying.
Fixes WIN-2047
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Module names parsed from user-supplied lock content (parse_modules_json)
and script imports (parse_script_imports) were interpolated directly into
PowerShell single-quoted string literals when building the module install
command, with no escaping or validation. A module name containing a single
quote could break out of the literal and execute arbitrary PowerShell.
Two-layer fix:
- Validate module names against ^[a-zA-Z0-9._-]+$. parse_modules_json
rejects invalid names with an error; parse_script_imports skips them
with a warning.
- Defense-in-depth: escape single quotes (' -> '') on both name and
version before interpolating into the install hashtable literals,
mirroring val_to_pwsh_param.
Fixes WIN-2049
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The flow editor graph pane collapsed to height 0 because FlowBuilder's
root used `flex flex-col flex-1 h-screen`. The `flex-1` (flex-basis: 0%)
overrides `h-screen` for main-axis sizing, so the div's height came from
flex-grow (indefinite) rather than the 100vh anchor. Every descendant
down to the graph relies on `height: 100%`, which only resolves against a
definite parent height, so the whole chain collapsed.
This was inert until the AIChat layout unification wrapped FlowBuilder in
a `flex flex-col` column, which activated `flex-1`. Drop `flex-1` to match
the working ScriptBuilder pattern (`flex flex-col h-screen`) used in the
same AiChatLayout, so `h-screen` stays the definite height anchor.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: scope AI session browser storage to the logged-in user
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: address review nits in user-scoped session storage
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: store AI session list in per-user IndexedDB via shared userScopedDb
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: cover legacy chat-history migration; address review nits
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: harden chat-DB writes and session-key cleanup per PR review
- route HistoryManager writes through whenReady() so they can't land in a
previous user's DB after an in-place user switch (drop cached this.indexDB)
- dedup the legacy chat-DB claim to one session-wide promise so racing
manager instances can't issue blocking concurrent deleteDB calls
- delete bare windmill_sessions keys unconditionally even when the user DB is
already populated, closing a partial-migration leak window
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: trim package-lock to only the fake-indexeddb addition
npm install -D had also stripped "dev": true from ~34 unrelated optional
native-binding packages (npm graph recomputation). Restore main's lockfile and
graft in only the fake-indexeddb node, so the lock diff is exactly the intended
dev dependency and prod-install classification of those bindings is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Db draft removal
* refactor: drop unsaved-changes confirmation modal from editors
* fix: remove nodraft from flow row edit link
* fix: remove nodraft from app and raw app edit buttons
* fix: remove nodraft from all edit links
* fix: merge backend defaults into legacy autosaves to avoid spurious restore toast on raw apps
* feat: add username column to draft table for user-scoped drafts
* feat: add sync_drafts and list_users_with_draft_on_path endpoints
* feat: add UserDraftDbSyncer service for bi-directional draft sync
* feat: wire UserDraft.save through DbSyncer + conflict modal
* refactor: gate useLocalStorageValue nested-update effect behind opt-in flag
* refactor: move sync force flag from request-level to per-entry
* feat: sync all userdraft kinds, switch draft owner to email FK, add id PK, scope draft list to readable paths
* refactor: route draft permission check through authed.folders + RLS, drop client-supplied email
* feat: support draft deletion via sync (value: null) with same conflict semantics
* feat: surface other users' drafts in editors with diff+fork action
* refactor: unify draft schema migrations and type kinds via DRAFT_KIND enum
* perf: add (workspace_id, email, created_at) partial index for sync hot path
* chore: update ee-repo-ref to a30079e75dc5b7d7413aa8ee20e40e80bfea9cbd
This commit updates the EE repository reference after PR #597 was merged in windmill-ee-private.
Previous ee-repo-ref: 55c19293232be379a3044eb78f677b545882ffd6
New ee-repo-ref: a30079e75dc5b7d7413aa8ee20e40e80bfea9cbd
Automated by sync-ee-ref workflow.
* fix(userdraft): trigger sync on deep mutations via readFieldsRecursively
* Rollback UserDraft
* remove queuing logic
* pushDrafts
* refactor: remove draft sync layer and conflict modal
* feat: add save_draft, list_drafts, get_draft routes
* feat: add get_draft overlay to getScriptByPath
* feat: extend get_draft overlay to flow, app, resource, variable, schedule, triggers
* feat: support null value in save_draft for deletes
* readLastSyncMap
* feat: redirect /add pages to /edit/draft_uuid with new_draft flag
* fix: inline get_draft query field instead of flattening
* fix: drop dangling nobackenddraft assignment in flows edit
* feat: include user drafts in list endpoints with is_draft flag
* fix: prefix draft paths with u/{user} and seed editor state on new_draft
* fix: route draft-only deletes through UserDraftDbSyncer on home page
* feat: delete user drafts when their underlying item is deleted
* fix: empty path seed on new_draft so friendly auto-name fires
* feat: re-add Draft and Draft only badges on home page rows
* fix: synthesize value wrapper on draft-only raw_app response
* fix: tolerate missing latest-version on draft-only flow reload
* fix: skip first observable change in DB sync effect to match LS persist
* fix: remove URL-hash sync from script editor (already marked TEMP)
* refactor: drop localStorage layer from UserDraft
* refactor: drop vestigial LS-era code from UserDraft
* feat: migrate localStorage drafts to DB on layout mount
* fix: migrate session runtime + script view to per-user draft API
* feat: add 'Reset to deployed' action on draft-loaded toast
* feat: hide 'Reset to deployed' action when no deployed version exists
* createCoalescingKeyedRunner
* example ts doc
* createDebouncerByKey
* refactor: drop await on draft-delete in reset flows, refetch deployed directly
* fix: bridge saved-draft shape to wire shape in apps/resources/variables loaders
* feat: route UserDraftDbSyncer.save through debouncer + coalescing runner
* feat: add immediate-save bypass that cancels pending debouncer + runner tasks
* fix: seed UserDraft cell from spec defaultValue on acquire
* fix: redirect /add routes at load phase to eliminate white flash
* fix: drop +page.js files in /add routes that conflicted with +page.ts
* refactor: send draft as separate .draft field instead of deep-merging onto deployed
* feat: surface draft path in home list when user typed one different from URL
* feat: add UserDraft.stopSync/restartSync, wire on script + low-code app /add init
* fix: thread URL path into ScriptBuilder.stopSync (was using empty initialPath)
* fix: also stopSync in route's new_draft branch + queue pre-acquire suspensions
* feat: add AutosaveIndicator backed by reactive UserDraftDbSyncer.getState
* refactor: drop draft-loaded toast in non-route editors, banner now compares draft vs deployed
* fix: gate per-user draft-only rows in listings on include_draft_only flag
* feat: flush pending draft saves via keepalive fetch on tab hide / pagehide
* autosave indicator nits
* fix: route create-vs-update on /add deploys; seed policy.execution_mode; sync script template
* chore: add [draft-sync] console logs to trace script bootstrap autosave
* fix: seed auto-generated path in script new-draft route to suppress Path widget's autosave-triggering mutation
* fix: defer script restartSync until script.path lands (Path widget gated on $userStore + $workspaceStore)
* fix: poll script.path via tick() until Path widget settles before restartSync
* chore: log inferArgs underlying error on deploy to diagnose 'Could not parse code' toast
* fix: wait for script.path to stabilize across two ticks before restartSync
* revert: drop unsuccessful path-stabilization heuristics + leftover [draft-sync] logs
* fix: seed new-draft script schema as emptySchema() so inferArgs doesn't trip on undefined properties
* fix: heal legacy drafts with schema={} (no .properties) on deploy
* autosave indicator
* refactor(editors): drop UnsavedConfirmationModal mount + Show diff button
* feat(drafts): collaboration banner, cross-tab conflict detection, raw app template picker
- Other-users-drafts banner (Modal2): the deployed-overlay response now
carries `other_drafts_users` (workspace usernames only, never emails);
each row offers View JSON + Fork. Drops the standalone
`listUsersWithDraftOnPath` endpoint; `getDraftForUser` now takes a
workspace `username` query param (resolved to email server-side).
- Cross-tab/browser save conflict detection: the syncer attaches
`last_sync` to every save (defaults to non-force); on a `conflict`
response it parks a snapshot in a reactive map. Each route mounts a
`DraftSyncConflictModal` and seeds the per-tab `last_sync` via
`recordRemoteSync(query, draft_saved_at)` on every `get_draft` load.
Keepalive flush also respects optimistic concurrency.
- Raw app template picker re-added after the /add ⇒ /edit refactor:
framework (React 19 / 18 / Svelte 5), data table + schema config, and
optional AI prompt — extracted into `RawAppTemplatePicker.svelte` and
driven by `new_draft=true` on the edit route.
* fix(drafts): suppress autosave during /add template seeding on script + raw app editors
- ScriptBuilder: delay `restartSync` 500ms past `initContent` + stores-
ready so the Path widget's `$workspaceStore && $userStore`-gated
`initPath → reset → onMetaChange → bind:path` cascade lands inside
the suspension window. Two `tick()` waits weren't enough — the
bind:path mutation fired ~100ms after the prior `restartSync` and
posted as a "user edit".
- apps_raw route: suspend autosave on `new_draft=true` and resume only
after the framework picker closes (via `onStart` or X dismissal),
with a two-tick settle so the picker's seeded
`files/runnables/data/policy` mirror to `draftHandle.draft` observably
advances `lastSerialized` before sync re-arms.
* fix(drafts): land /add redirects on the real workspace username, not "me"
The `/add` → `/edit/u/{username}/draft_{uuid}` redirects ran during
SvelteKit's load phase, BEFORE the (logged) layout's async `getUserExt`
populated `userStore`. `get(userStore)?.username` returned undefined and
fell back to the `'me'` placeholder on every fresh nav, producing
`u/me/draft_{uuid}` paths instead of the user's real namespace — broke
ownership checks against `authed.username` and silently scoped autosaves
under the wrong path.
Layout now persists `username` to localStorage on every successful
`getUserExt`, and `getUsernameForNamespace` (new shared helper, used by
all four `/add/+page.ts` files) reads the live store first, falls back
to the cached value, and only then to `'me'` for true first-ever loads.
* fix(drafts): key low-code app autosave on the URL path, not the empty string
`AppEditor` keyed its `UserDraft.use` handle on `newApp ? '' : path` —
a legacy leftover from when `/apps/add` was its own URL (no path). With
the `/add` ⇒ `/edit/u/{user}/draft_{uuid}` redirect, `newApp=true` made
autosaves land on the `('app', '')` row instead of the URL path:
- The `apps/list?include_draft_only=true` query joins drafts onto
`app.path`, surfacing drafts at the URL path. The empty-path row
didn't match the user's URL so the draft never appeared in the home
list.
- Refreshing `/apps/edit/u/{user}/draft_{uuid}` re-fetches at the URL
path with `?get_draft=true`, finds nothing, and 404s.
Drop the ternary so the handle always uses `path` — the same as
scripts/flows/raw_apps. The route's `?new_draft=true` branch already
seeds the empty-template baseline, so there's no longer a "the
draft sits under '' until first save" race to worry about.
* fix(raw_app): propagate template picker X / Esc dismissal so autosave resumes
The picker mounted `<Modal kind="X" open ...>` (one-way prop, not
`bind:open`). When the user dismissed via X / Esc / click-outside, the
inner Modal flipped its own local `open` to false (hiding the UI) but
never wrote back to the picker's `open` $bindable. The route's
`templatePicker → false` watcher — the one that calls `restartSync`
two ticks after the picker closes — never fired, so autosave stayed
suspended and the user's edits after dismissal were silently dropped.
Switch the inner Modal to `bind:open` so the dismissal bubbles all the
way up to the route's state. "Start without AI" already worked because
its `onStart` handler explicitly sets the picker's `open = false`.
* nit unused
* fix(drafts): make the home-page View/Edit JSON action work on draft-only apps
The "View/Edit JSON" entry on the home page called `AppService.getAppByPath`
without `get_draft=true`, so for draft-only items at `u/{user}/draft_{uuid}`
the backend 404'd with "App not found at path …". Pass `get_draft=true`
and render the synthesized stand-in's editable shape:
- App drafts come back as `{summary, value, path, policy, ...}` — `value`
is the App definition the editor was working on; show that.
- Raw-app drafts come back as the flattened
`{files, runnables, data, summary, policy, ...}` with no nested `value`;
show the whole shape.
On save, draft-only items can't go through `updateApp` (no deployed row).
Route the edit through `UserDraftDbSyncer.save` (with `immediate: true`
so `await` resolves after the POST lands) and relabel the button
"Save draft" + Save icon. Deployed items keep the existing "Deploy"
flow unchanged.
* fix(drafts): render the right shape in View/Edit JSON for draft-only items
The previous fix landed `fapp.value` into the editor, but the
deployed-overlay flattens the bare editable shape into `inner`/the
top-level response — drafts have no nested `.value`. So:
- App drafts (`{grid, breakpoints, hiddenInlineScripts, …}`) rendered
as empty (`fapp.value` was undefined).
- Raw-app drafts 404'd outright: `get_draft=true` with no `rawApp` flag
can't tell which draft kind to look up, defaults to `app`, doesn't
find one.
Thread the row's `raw_app` flag from AppRow → `appExport.open(path,
rawApp)` → `getAppByPath({..., rawApp})` so raw-app drafts resolve to
the right `UserDraftItemKind`. Read `fapp.draft` (the bare editable
shape from `fetch_draft_only`) into the JSON editor for draft-only
items — clean payload, no `is_draft` / `no_deployed` / overlay noise.
Save the same bare shape back through the syncer so the regular
editor reads it unchanged on the next mount.
* fix(drafts): skip public-secret-URL fetch in the Deploy drawer for draft-only apps
Opening the Deploy drawer on a `/edit/u/{user}/draft_{uuid}` app fired
`AppService.getPublicSecretOfApp` immediately because the gating effect
only checked `appPath != ''` + `savedApp`. The `/secret_of/{path}` route
plain-SELECTs `app.id`, so a draft-only path 404'd with
"App not found at name …" and the public-URL ClipboardPanel spun
forever waiting on `secretUrl`.
Thread the existing `newApp` signal (already on `AppEditorHeader` /
`RawAppEditorHeader`) into `AppEditorHeaderDeploy`, gate the fetch
behind `!newApp`, and render the existing "Deploy this app once to get
the public secret URL" placeholder instead of the spinner for
draft-only items.
* fix(drafts): disable Diff button on draft-only items across the 4 editors
Diff has no baseline to compare against on draft-only items — the
button used to be gated by the pre-PR `/add` route's own state, but the
`/add → /edit` redirect landed everything under the regular `/edit`
page where the gate was missing.
- ScriptBuilder: gate the topbar Diff on `savedScript.no_deployed`;
seed `no_deployed: true` on the route's `new_draft` empty NewScript
so the gate fires before the first deploy.
- FlowBuilder: gate the topbar Diff on `newFlow` (route already sets
it from `backendFlow.no_deployed` and the new-draft branch).
- AppEditorHeader: gate both the "Diff" dropdown action and the
Deploy-drawer's "Diff" button on `newApp`.
- RawAppEditorHeader: gate the topbar Diff + the Deploy-drawer's "Diff"
button on `newApp`.
Each gate also rewrites the tooltip ("Deploy this … once to compare
against the deployed version") so the hover state explains why.
* fix(drafts): disable the "No login required" toggle on draft-only apps
Flipping the toggle called `setPublishState`, which POSTs the new
`policy` through `AppService.updateApp` — that handler's
`UPDATE app ... RETURNING path` finds nothing on a draft-only path
and `not_found_if_none` 404s with "App not found at name …"
(apps.rs:1975). Gate the Toggle on `!newApp` too so the user has to
deploy once before configuring the publish state.
* refactor(drafts): drop dead draft_path field from list responses
The draft-only listing branches in scripts/flows/apps computed a
`draft_path` from the draft JSON (when the user-typed path differed from
the URL's autogenerated `u/{user}/draft_{uuid}`), and `{Script,Flow,App}
Row.svelte` preferred it over `path` for the row title. In practice
that path is never written: the app, raw-app and flow editors all warn
"Deploy the X to make the path change effective" — the rename only
lands on deploy, never in the draft. So the field is always None and
the home rows always show the autogenerated slot anyway.
Drop the field from the three `Listable*` structs, the three draft-only
push sites, the three OpenAPI response schemas, and the three frontend
row components. Client regenerated.
* fix(drafts): seed a friendly name on /flows/add
The flow route passed `initialPath={page.params.path ?? ''}` to
FlowBuilder, so on the `/flows/add → /flows/edit/u/{user}/draft_{uuid}`
redirect the Path widget's `initPath` saw a non-empty `initialPath` and
skipped the `reset()` branch that auto-generates the friendly
`<random_adj>_flow` name. The other three editors all clear
`initialPath` in their `new_draft` branch for exactly this reason.
Track `initialPath` as route-owned state (defaults to the URL path) and
clear it to '' inside the `new_draft` branch, then bind it through to
FlowBuilder so any post-deploy update from the editor still propagates.
* feat(drafts): render friendly user-typed path on home list for all 4 kinds
Reinstate `draft_path` on `Listable{Script,Flow,App}` so the home rows
prefer the user-typed name over the autogenerated `u/{user}/draft_{uuid}`
URL slot, with two source rules — one per how each editor wires the
Path widget:
- Scripts already work: `ScriptBuilder` binds the Path widget directly
to `script.path`, so the typed path round-trips through the draft
JSON's own `path` field. Backend extracts `v["path"]` when it differs
from `row.path`.
- Flows / apps / raw apps don't write the typed path into the
autosaved value (`Flow.path` is one-way-bound to `$pathStore`; the
bare `App` / raw-app value has no `path` field at all). Introduce an
explicit `draft_path` field on the draft JSON, written by the editor
ONLY when the typed path differs from the deployed/seeded
`savedX.path`:
- FlowBuilder: $effect on `$pathStore` mutates `flow.draft_path`.
- AppEditorHeader: $effect on `newEditedPath` mutates `$app.draft_path`.
- RawAppEditorHeader: $effect surfaces `pendingDraftPath` up via the
bind chain (RawAppEditor → route); the route's draftHandle.draft
spread includes `draft_path` when set.
Backend extracts `v["draft_path"]` and `None` when unchanged or after
deploy (deploy clears the whole draft, so the field naturally
disappears post-deploy without bookkeeping).
Flow route's `new_draft` branch now stops sync around the Path widget
cascade, with a 700ms scheduled `restartSync` (mirrors the existing
scripts/apps/raw_apps stoppers) — the new draft_path mutation lands
inside that window so `/flows/add` no longer fires an autosave before
the user's first edit. openapi/sqlx regenerated.
* fix(drafts): preserve the user-typed draft_path on reload of draft-only items
The flow / app / raw-app editors all dropped the saved `draft_path`
back to the URL's `u/{user}/draft_{uuid}` slot the moment the user
reloaded a draft-only edit page: the route sourced the Path widget's
initial path from `page.params.path` instead of the previously-saved
`draft_path`, and the first user edit then mirrored that URL path
back into the autosaved draft — silently overwriting the friendly
name in both the row and the editor.
- Flow route: after computing `effectiveFlow`, override `flowInitialPath`
with `effectiveFlow.draft_path` when set.
- App route: pass `newPath={(app.value as any)?.draft_path ?? app.path}`
through to `AppEditor`; AppEditorHeader's `newEditedPath` default now
prefers a non-empty `newPath` over the random `<adj>_app` seed (the
`newApp && !newPath` branch keeps the `/apps/add` friendly auto-name).
- Raw-app route: surface `savedRawAppDraft.draft_path` onto `backendApp`
so the `extractRawApp` path seeds `newPath` with the friendly name.
Reload + a subsequent edit now leaves `draft_path` intact for all three
kinds; verified end-to-end via the `/drafts/get_draft/...` endpoint.
* fix(ui): default Modal2 target to 'body' so omitting the prop doesn't throw
Modal2 defaulted `target = ''` and forwarded it to `Portal`, which calls
`document.querySelector(target)` — an empty selector throws
"Failed to execute 'querySelector' on 'Document': The provided selector
is empty" and the modal silently fails to mount.
That's why `OtherUsersDraftsModal` (and `DraftSyncConflictModal`) never
appeared on editors where another user had a draft — both omit the
`target` prop. Other Modal2 callers (StorageSettings, CriticalAlert,
CustomInstanceDbWizardModal, …) pass an explicit `target="#content"`
and were unaffected.
Match Portal's own default of `'body'` so omitting the prop is now a
no-op rather than a runtime throw.
* fix(drafts): Reset to deployed no longer resurrects the draft
The toast's "Reset to deployed" callback POSTed `value: null` to the
syncer, then handed control to the route's `onResetToDeployed` (which
wipes the in-memory handle and reloads the deployed payload via
`getDraft: false`). Both writes flowed through the reactive sync
effect: the wipe scheduled a delete, the reload scheduled a re-save of
the deployed value as the new draft. Coalescing collapsed them and the
draft came back — making the "discard" action effectively a no-op.
Wrap the whole callback in `UserDraft.stopSync` / `restartSync`. The
explicit `value: null` POST still goes through (it's a direct
`UserDraftDbSyncer.save` that doesn't depend on the reactive effect),
the route's wipe-then-reload mutations advance `lastSerialized` silently
under suspension, and the next user edit (after two ticks past the
deployed-seed write) is the first real save again.
* ui nit
* feat(drafts): autosave-indicator popover with Reset-to-deployed action
Click the cloud icon → popover with "All changes are saved as a draft on
the server. The draft is per-user — your teammates' editors keep their
own." When the editor isn't on a draft-only path AND the user has a
draft (UserDraft.has returns true), a "Reset to deployed" button
mirrors the load-time toast action — stops sync, POSTs `value: null`,
runs the route's reload-without-draft callback, restarts sync past two
ticks so the deployed-seed write doesn't resurrect the draft.
Threaded `onResetToDeployed` from each route down to its builder
(ScriptBuilder / FlowBuilder / AppEditorHeader / RawAppEditorHeader)
and into the indicator. `draftOnly` is wired from `savedScript.no_deployed`
/ `newFlow` / `newApp` so the action hides where there's nothing to fall
back to. The indicator's trigger now has a hover affordance + matches
Portal's default target ('body') via Modal2's earlier fix.
* fix(drafts): wait for the fork POST to land before navigating
OtherUsersDraftsModal's Fork action called UserDraft.save, which routes
through the autosave debouncer (1500ms). The subsequent goto fired
within the same tick, so the destination editor's get_draft=true read
ran before the POST landed and 404'd — refreshing worked because by
then the debounced save had fired.
Call UserDraftDbSyncer.save with immediate: true and await it. The
syncer cancels any queued debouncer task for the key and resolves the
promise only after the POST completes, so the route load can find the
forked draft on the first try.
* fix(drafts): conflict detection — keep last_sync map tab-local instead of in localStorage
Two tabs editing the same draft both load with last_sync = T0.
Tab-1 saves; the server accepts, returns T1, and the syncer wrote T1
into localStorage. Tab-2 then tries to save: it reads the SHARED
localStorage map, sees T1 instead of its own baseline T0, sends
last_sync = T1, and the backend's WHERE clause (`created_at <=
last_sync`) is true → tab-2 clobbers tab-1's edit without ever seeing
a conflict.
Move the map to tab-local memory (`new Map<string, …>`). Reload of the
tab now starts with an empty map; that's fine because the editor's
load path calls `recordRemoteSync(query, draft_saved_at)` right after
`get_draft=true` returns, reseeding from the authoritative server
timestamp before any user edit could fire a save.
* fix(drafts): OtherUsersDraftsModal — close on Fork, don't leak clicks through nested JSON
Two bugs in the per-editor "another user has a draft" banner:
- Fork landed the immediate save but didn't close the banner before
navigating. Svelte hadn't torn down the previous route's components
by the time goto returned, so the banner lingered on top of the
destination editor. Comment the explicit isOpen=false on the
happy path so it's clear it MUST run before goto.
- Clicking anywhere on the screen while the View JSON drilldown was
open closed the underlying banner too. Modal2's clickOutside
action fired on every Modal2 instance — both the JSON modal and
the underlying banner — because both attach their own listener at
the document level. Add `closeOnOutsideClick` opt-out on Modal2
and pass `closeOnOutsideClick={!jsonOpen}` to the outer modal so
clicks outside the JSON drilldown only close the drilldown.
Drive-by: Modal2's keydown handler now ignores Escape when its own
isOpen is false (was a no-op closer that would still preventDefault
on every key press, swallowing key events for any siblings).
* fix(drafts): conflict modal wording — drafts are user-scoped, not teammate-scoped
* fix(drafts): defer reset-to-deployed restart until first user interaction
Two-tick `restartSync` was too aggressive: editor remounts emit a tail
of cascading writes (Monaco setValue acks, schema re-infer, UI Builder
iframe handshakes, schedule-config recomputes, …) that land well after
two ticks and would clobber the just-deleted draft with an upsert of
the deployed value — making "Reset to deployed" a no-op in practice,
the user kept seeing the draft come back.
Centralise the suspension lifecycle in a new `runResetToDeployed`
helper. It stopSyncs around the reset, POSTs the explicit delete, runs
the route's wipe-and-reload, and then arms a one-shot listener on
document keydown / input / pointerdown that restartSyncs on the user's
next real interaction. A 5-second fallback re-arms sync if the user
walks away without touching the editor, so suspensions don't leak.
Use it from both the load-time toast (`notifyDraftLoaded`) and the
autosave-indicator popover so the two stay in sync — fixes both
entry points.
* indicator ui nits
* fix(drafts): split tab-switch and unload flushes — kill self-conflict on visibility change
The single keepalive flush bound to both `visibilitychange → hidden`
and `pagehide` self-conflicted on tab switch: visibilitychange fires
on every tab/app switch with the page still alive, the keepalive POST
advanced the server's `created_at` to a fresh `now()`, the client
discarded the response (no listener), the local `lastSync` stayed at
the old value, and the next foreground autosave sent that stale
timestamp → server saw `created_at > last_sync` → conflict modal for
the user's own background-tab write. A still-pending debouncer task
made it worse: it fired a second runner POST after the keepalive with
the same stale `last_sync`, the second self-conflicted too.
Split into two paths:
- `visibilitychange → hidden` → `flushOnVisibilityHidden`: route
through the normal runner pipeline. The page is alive, so the
response can land and `setLastSync` keeps the baseline current. Call
`debouncer.cancel(key)` first so a queued keystroke can't double-fire
with the same stale `last_sync`.
- `pagehide` → `flushOnPageHide`: keep the `keepalive: true` raw fetch
for the genuinely-going-away case (the JS context is torn down, the
response is necessarily discarded). Same `debouncer.cancel(key)`
guard. On the next mount, the route's `recordRemoteSync(query,
draft_saved_at)` reseeds `lastSync` from authoritative server state
before any user edit can fire a save.
* fix(drafts): drop the visibilitychange flush — debouncer keeps running on hidden tabs
Tab switching just hides the page; the JS context survives and the
debouncer's `setTimeout` keeps counting down. When it fires, the runner
POSTs normally and the server's response updates `lastSync`. There's
nothing left for a visibilitychange-driven flush to do that the
ordinary pipeline doesn't already handle, and adding one only creates
extra POSTs to reason about.
`pagehide` remains the single trigger for the keepalive flush — that's
the case where the JS context is actually being torn down and the
runner's pending fetch would otherwise be killed mid-flight.
* nit
* refactor(drafts): drop LS-era pipeline; backend is canonical on load
The PR's iteration left behind a meta/staleness pipeline carried over
from the localStorage era — per-rev tracking, a LocalDraftStaleModal, a
'Restored from local storage' toast, and a localDraft-vs-backend
comparison branch in every editor loader. With drafts now living in
the DB and the optimistic-concurrency lastSync check handling
divergence, that whole stack is dead weight.
Worse, the comparison branch caused 'Load from server' in the conflict
modal to do nothing: the loader preferred the in-memory cell over the
backend, so the user-clicked 'load from server' just re-displayed the
local edits AND fired two confusing toasts (Restored from local
storage + Loaded your saved draft).
The rip:
* userDraft.svelte.ts: drop UserDraftMeta, StoredDraft.meta,
checkStaleness, UserDraftStalenessCause, normalizeForCompare,
localDraftDiffers, saveMeta, getMeta, setDraftAndMeta, setMeta,
handle.meta/setDraftAndMeta/setMeta, force option. Handle is now
just { draft }.
* userDraftToast.ts: drop notifyRestoredFromLocal +
RestoreFromLocalActions. Update copy.
* LocalDraftStaleModal.svelte: deleted.
* AppEditor.svelte: drop initialRevs prop and the firstMirror
wipe-then-restore dance (it existed only to consume the meta-mismatch
skip slot).
* All 4 editor routes: backend is canonical on load — the in-memory
cell is overwritten with the deployed+draft overlay, the syncer's
seed guard swallows the first write so we don't POST it back.
* VariableEditor / ResourceEditor: drop the staleness pipeline + rev
bookkeeping; backend wins on open.
* useTriggerDraftSync.svelte.ts: inline the JSON-normalize + deepEqual
utility as a private cfgDiffers helper (kept for the form-vs-deployed
dirty check, which is a genuine semantic compare, not LS legacy).
* copilot core.ts / userDraftAdapter.ts: drop meta argument from
saveAppDraft, loadAppDraftValue, write*Draft. Test assertions on
getMeta dropped.
Net: -22 typecheck errors, fewer moving parts, conflict modal works.
EOF
)
* refactor(drafts): remove dead endpoints + UserDraftDbSyncer.getLastSync
The list_drafts and get_draft (own) routes were added during PR
iteration and never wired up to any frontend caller — the editor
overlay path uses the per-kind get-by-path getDraft query parameter,
and the home page lists drafts via the per-kind list endpoints, not
via /drafts. Drop both routes (+ sqlx caches + OpenAPI entries).
UserDraftDbSyncer.getLastSync was a peep-hole for callers that never
materialised — the per-tab lastSync map is only ever read by postSave
internally, where the bookkeeping already lives inline.
* refactor(drafts): extract DraftEditorModals trailer block
The four editor routes (scripts/flows/apps/apps_raw) mounted an
identical pair of trailer modals — DraftSyncConflictModal +
OtherUsersDraftsModal — wrapped in the same guard chain and {#key path}
remount. Lift the markup into one component; routes thread their
itemKind, path, editPathFor, and loader callback.
Pure markup extraction, no state ownership change. Drops the unused
userStore import where the trailer was the only consumer.
* refactor(drafts): UserDraft.useReactive — kill array-of-one boilerplate
The script + flow routes both wanted a handle that re-keys when the URL
path changes. UserDraft.use() can't do that (its opts getter is
untracked), so each route hand-rolled the same useMany-array-of-one +
proxy idiom:
const handles = useMany(() => [{ kind, path: reactive }])
const handle = { get draft() { return handles[0]?.draft }, ... }
Add UserDraft.useReactive(getSpec) that internally wraps useMany with a
single spec and returns the stable proxy. Callers collapse to one line.
* refactor(drafts): unify bootstrap suspension via armRestartOnFirstInteraction
The flow and raw-app routes each rolled their own end-of-bootstrap
resume: a 700ms setTimeout for flows and a templatePicker watcher with
double-tick gating for raw-apps. Both are timing-fragile (the comments
admit it) and drift from each other.
armRestartOnFirstInteraction already existed in userDraftToast.ts for
reset-to-deployed: keydown/input/pointerdown listeners (capture phase)
that fire restartSync on the first real user touch, with a 5s
belt-and-braces fallback. Export it and use it everywhere we'd previously
have picked a magic number.
For raw-apps this is a tiny behavioural change: the user's template
choice now POSTs immediately (the pointerdown that picks the template
also resumes sync, so the picker's onStart write rides the wake-up).
Previously the choice only persisted on the user's NEXT edit. That's
strictly better — navigating away preserves the choice now.
* refactor(drafts): type App.draft_path; drop the as-any cast
The audit asked for the three editors to converge on one draft_path
injection pattern. For App and Flow, the in-builder $effect-mutates-
the-store idiom is wedged into a shape that doesn't natively own the
field — App's editor type genuinely has no draft_path so the writer
had to cast through `as any`, and consumers downstream did the same.
The minimum viable fix: declare draft_path on the local App type
(it's already a field on the autosaved JSON). Lifting the writes
upward into a route-side merger would mean restructuring the
AppEditor mirror $effect and the FlowBuilder pathStore plumbing —
larger change for the same shape, deferred to a follow-up.
Flow already has the typed cast localised at one site. Will get the
OpenAPI-level draft_path field as part of task 47 (drop as-any
casts on backend overlay reads).
* refactor(drafts): extract makeDraftAddLoad helper
Four identical /add/+page.ts files differing only by the edit-route
prefix. Lift the redirect into a factory, slim each entry point to
two lines.
* refactor(drafts): type UserDraftOverlay.other_drafts_users in the OpenAPI
The backend response carried other_drafts_users on every get-by-path
that supports the draft overlay, but the OpenAPI schema didn't declare
the field. Each route had to cast the typed response to `any` to read
it (and the sibling draft_saved_at), which obscured the real shape from
the type system and rotted the discoverability of the draft surface.
Add it to UserDraftOverlay. Frontend casts collapse to plain property
reads in the three editor routes.
* feat(drafts): list & open draft-only items for variables, resources, schedules, triggers
For scripts/flows/apps the list and get-by-path endpoints already
surface per-user drafts that have no deployed counterpart — that's
what gates the home page from 404'ing on an AI-agent-created draft.
Extend the same support to the other UserDraftItemKinds:
Backend (list endpoints):
- Add include_draft_only to ListVariableQuery, ListResourceQuery,
ListScheduleQuery, StandardTriggerQuery (the latter covers the
11 trigger kinds via the generic TriggerCrud).
- Append per-user draft rows whose path has no deployed row. Same
gate as scripts/flows/apps: non-operators, page 0, no narrowing
filters. Synthesis is per-kind: ListableVariable/Resource get
field-for-field synthesis; ScheduleLight reads NewSchedule shape;
Trigger<T> uses a best-effort JSON merge + serde_json::from_value
(rows skipped on deserialize failure rather than failing the list).
- Add draft_only: Option<bool> with sqlx(default) to each row type
so it serializes as the column is opt-in.
Backend (get-by-path endpoints):
- get_variable, get_resource, get_schedule, get_trigger<T> fall back
to fetch_draft_only when the deployed row is missing and the
caller passed get_draft=true. Mirrors scripts/flows/apps.
OpenAPI:
- Shared IncludeDraftOnly parameter under components/parameters,
wired into the 11 trigger list endpoints + listRawApps. Inline
declarations on listVariable / listResource / listSchedules /
listAzureTriggers.
- draft_only field on ListableVariable, ListableResource,
Schedule, TriggerExtraProperty.
Frontend:
- variables, resources, schedules, and the 10 trigger list pages
(routes + 9 *_triggers) pass includeDraftOnly: true on the
initial fetch and render <DraftBadge draft_only> on synthesized
rows. Trigger pages got a sed/perl bulk update — pattern is the
same across kinds.
* fix(drafts): swap crypto.randomUUID() for the project's randomUUID helper
crypto.randomUUID() is gated on a secure origin (HTTPS or localhost).
Self-hosted Windmill instances often run on a bare HTTP origin or a
LAN IP where the WebCrypto API is unavailable, so the /add redirect
would throw before issuing the 307. Use the existing RFC4122 v4 helper
in FlowChatManager that the rest of the codebase already imports for
this exact reason.
* fix(editor): leading-edge fire + max-wait cap on Monaco debounce
The Editor debounced `onDidChangeModelContent` purely on the trailing
edge — every keystroke rescheduled a 500ms timer, and uninterrupted
typing held the bindable `code` prop stale until a pause. Stacked
behind our 1.5s autosave debouncer that meant our clock didn't even
start ticking until 500ms after the user paused, and the `code`
binding never updated mid-burst for downstream consumers (lint,
live preview, change listeners).
Switch to leading + trailing + max-wait:
* First keystroke of a burst fires `updateCode` synchronously, then
stamps a wall-clock chain start.
* Each subsequent keystroke (re)arms a trailing timer at
`min(now + changeTimeout, chainStart + maxChangeTimeout)` — the cap
is what makes continuous typing materialize at least once per
maxChangeTimeout window instead of indefinitely.
* When the trailing fires it resets the chain so the next keystroke
after a pause is a fresh leading fire.
New prop `maxChangeTimeout` (default 1000ms) sits next to the
existing `changeTimeout` (default 500ms). Dispose path clears the
chain stamp alongside the timer.
* feat(drafts): wire Ctrl/Cmd+S to flush the pending autosave immediately
Each builder already had a Ctrl/Cmd+S keybinding routed through a
saveDraft() no-op left over from the LS-era — the comment said
"persistence happens via the page-level UserDraft autosave" but the
shortcut was the user's only way to actually force a save without
waiting for the 1.5s debounce. Restore the intent.
* UserDraftDbSyncer.flush({ workspace, itemKind, path }) — new method
that re-submits whatever's queued in pendingSaveOpts with
immediate: true. No-op when nothing's pending.
* Editor.svelte.flushPendingChanges() — exposes a synchronous
updateCode() with chain reset, so callers can drain Monaco's own
trailing debounce before asking the syncer to flush. Without this
step a Ctrl+S within ~500ms of typing would POST the pre-burst
content.
* ScriptBuilder.saveDraft() — editor?.flushPendingChanges() →
await tick() → UserDraftDbSyncer.flush(). Toast on result.
* FlowBuilder.saveDraft() — no direct Monaco ref (flows have many
per-module editors); just flushes the syncer. Editor.svelte's new
1s max-wait cap means at most the last <1s of typing in a module
Monaco won't be in this POST; it follows in the next autosave
round.
* RawAppEditor.handleKeydown — adds a 's' case that flushes before
the focus guard, so the shortcut fires regardless of where focus
is in the editor pane.
* fix(drafts): low-code apps — drop spurious autosave on /edit + remount on Load from server
Two bugs in low-code app editor (raw apps use a separate code path):
1. Every /edit visit looked like an autosave because loadApp() called
UserDraft.discard('app', path, undefined). The comment claimed
"this load doesn't POST" but discard always POSTs value: null
server-side — that surfaced as a DELETE-my-draft on every page
load AND a flash in the AutosaveIndicator.
The discard was originally intended to wipe the in-memory cell so
AppEditor remounts "fresh". But the path-change $effect upstream
already sets app = undefined before each loadApp, which unmounts
AppEditor and releases the handle's entry — so a remount via
app = backendApp naturally starts with an empty handle. Drop the
discard.
2. The conflict modal's "Load from server" called loadApp() but
didn't remount AppEditor. Since AppEditor's stateApp is captured
once at mount and doesn't react to prop changes, the editor kept
showing the conflicting local edits even after a successful reload.
Wrap the onLoadFromServer to await loadApp() then bump redraw to
force a fresh mount.
* feat(drafts): home-page Draft badge — show user-initial circles, drop the '+'
The home-page Draft badge previously showed '+Draft' as a flat label.
Add per-user awareness: up to 3 user-initial circles render to the left
of the label, ordered alphabetically; with 4+ users we collapse to the
first 2 + a '+N' overflow circle so rows stay compact.
Backend:
* New `DraftUserRef { username: Option<String> }` in
windmill-types::user_drafts, re-exported from windmill-common so the
list endpoints in scripts/flows/apps crates share one import path
(windmill-types/windmill-common can't be reordered without a cycle).
* ListableScript / ListableFlow / ListableApp gain a
`draft_users: Option<sqlx::types::Json<Vec<DraftUserRef>>>`
field. The list SQL adds a per-row subquery
`SELECT json_agg(...) FROM draft d LEFT JOIN usr u ...` that
aggregates the workspace users with a per-user draft at this path.
NULL (no drafts) decodes to None; LEFT JOIN against `usr` lets
orphaned drafts (user removed from workspace) still surface with
username = None.
* Synthesized draft-only rows set draft_users to a single-element
vector with the authed user (those rows come from `email = $2`).
OpenAPI: `draft_users` added to listScripts / listFlows / ListableApp
response shapes as an array of `{ username }` with nullable username.
Frontend DraftBadge:
* Accepts `draft_users: { username?: string | null }[]`. Renders up
to MAX_CIRCLES (3) initial circles; at 4+ users renders first 2 +
a gray '+N' overflow circle.
* Initials: 'john.doe'/'john_doe' → 'JD', 'alice' → 'AL', the legacy
NULL-email row → '?'.
* Color picked deterministically from a 6-entry palette so the same
user gets the same circle color across rows.
* Label is now just 'Draft' (dropped the '+'). 'Draft only' is
unchanged.
* Tooltip lists every user in full.
ScriptRow / FlowRow / AppRow thread `draft_users` through their
prop types and pass it to DraftBadge.
* fix(drafts): suppress 'You have unsaved changes' banner when deployed baseline is null
A brand-new variable/resource/trigger (no deployed row yet) has
`getDeployed() == null`, but the caller's `show` prop is computed
off `current != deployed` which is trivially true while the user
types. Result: the banner appeared with 'Show diff' (no-op — the
drawer early-returns on null deployed) and a 'Discard' that's
semantically backwards (there's nothing to revert to).
Gate `show` internally on `getDeployed() != null`. The check sits
in the banner rather than each caller because every caller would
otherwise need the same boilerplate guard.
* fix(drafts): hide LocalDraftBanner when deployed and current match the DiffDrawer's compare
Earlier I gated the banner on `getDeployed() != null`, but the user
still saw it fire on entries where 'Show diff' opens to 'No changes
detected'. That means `show` (the caller's coarse dirty check) flagged
a difference the DiffDrawer treats as a no-op — typically toggle
defaults (`false ↔ undefined`), removed empty arrays, or key-ordering
noise that `cleanValueProperties + orderedYamlStringify` collapses.
Replicate the drawer's comparison inside the banner: stringify both
sides through the same pipeline and only render when the keys differ.
A single `diffKey()` helper keeps the logic local; the catch-and-empty
fallback survives a non-serializable side rather than throwing.
* ui(drafts): nest user-initial circles inside the Draft badge
Previously the circles sat alongside the Badge in a parent flex
container; the result read as two separate UI elements. The Badge
component already exposes its children as a snippet rendered inside
its own flex row, so moving the circles into it makes them feel like
part of the same chip.
Knock-on tweaks: shrunk the circles from h-4/w-4 to h-3.5/w-3.5 so the
badge stays compact, and tinted each circle's ring with the badge's
indigo palette (instead of plain white) so the overlap reads as a
deliberate stack rather than dots floating on top of the chip.
* feat(drafts): drop the authed user's circle, mark own drafts with a '*' suffix
Three tweaks to the home-page Draft badge:
1. Filter the authed user out of `draft_users` before rendering
circles. The row already signals 'this user has a draft' via the
asterisk (below), so a circle for them would be redundant noise.
New `currentUsername` prop on DraftBadge — pass
`$userStore?.username` from each row. The tooltip still lists every
user (with `(you)` next to the authed one) so the full picture is
one hover away.
2. The badge already showed whenever `is_draft || draft_users.length > 0`
(per-user OR any-user). Spelled the rationale out in a comment —
no logic change.
3. Append '*' to the displayed summary when `is_draft` is true. Falls
back to `draft_path`/`path` when summary is empty so the marker
never decorates an empty string. Threaded the same expression into
ScriptRow / FlowRow / AppRow.
Slice/overflow math now keys on the post-filter `otherUsers` list, so
dropping the authed user doesn't silently shrink the visible count
(e.g. 3 users incl. self → 2 circles, not 1 circle + a '+1' bubble).
* feat(drafts): clone per-user drafts when forking a workspace
`clone_workspace_data` clones every other workspace-scoped table on
fork creation (resources, variables, scripts, flows, apps, raw apps,
triggers, schedules) but quietly dropped the `draft` table. With
per-user drafts that meant any open editor in the parent lost its
pending edits the moment a fork was created — surprising and
inconsistent with how forks treat the deployed surface.
New `clone_drafts` mirrors the existing clone helpers: a single
INSERT...SELECT into the target workspace, preserving `path`, `typ`,
`value`, `created_at`, and `email`. The `email` FK targets
`password.email` which is instance-scoped so it carries across
workspaces without remap. `created_at` is preserved on purpose so the
per-tab `last_sync` baseline lines up with the parent's timeline —
otherwise the fork's next autosave would race a stale `last_sync`
and trip the conflict modal on every cloned draft.
Plain INSERT (not UPSERT) is safe because the fork target is empty at
create time; no conflict against the partial unique indexes
(`draft_pkey_with_user` / `draft_pkey_legacy`). The synthetic
BIGSERIAL `id` PK is regenerated by the default so it stays out of
the column list.
* ui(drafts): pin the authed user to the first circle instead of hiding them
Previously the authed user was filtered out of the circle row entirely
on the theory that the row's '*' suffix already signalled 'this user
has a draft'. New requirement: they should always lead the circle row
when they have a draft so the visual half of the signal lines up
across rows (consistent leading-slot identity, easy scan).
Switch from a filter to a sort: `orderedUsers` finds the authed user
in `draft_users` and splices them to index 0; everyone else keeps the
backend's alphabetical order behind. Slice/overflow math now keys on
`orderedUsers`, which guarantees the authed user never falls into
the '+N' bubble — they're at position 0 and the slice keeps the head.
The popover's '(you)' annotation moves to the circle's title attr too,
so hovering the leading circle confirms the identity.
* feat(drafts): drop draft_only column from script/flow/app
Drafts now live in the `draft` table exclusively — `draft_only` stubs in
script/flow/app are redundant. Migration `INSERT INTO draft ... ON
CONFLICT (workspace_id, path, typ) WHERE email IS NULL DO NOTHING` so
real per-user drafts already at the same path are preserved; only rare
stubs that lost their draft get a synthesised workspace-level row.
Stubs are then deleted (FKs cascade to *_version) and the column is
dropped. List endpoints keep a synthesised `draft_only: true` on rows
sourced from the draft table itself (sqlx default on the struct field).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ui(drafts): surface draft state in AutosaveIndicator instead of toast+auto-modal
The "Loaded your saved draft" toast and the auto-opening
OtherUsersDraftsModal both surprised users on every editor mount. Move
both signals into the AutosaveIndicator label: "Loaded from draft" or
"Others are working on this {kind}" (priority) sits where Saving/Saved
do, with a one-shot light-green flash behind the indicator that fades
to transparent. Saving/Saved still win when they fire. The popover
gains a "See others' drafts" button that flips the modal open on
demand; the modal itself is now externally controlled via a bindable
\`isOpen\` threaded through DraftEditorModals.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ui(drafts): per-user View JSON / Fork actions in DraftBadge popover
Hover popover used to be a plain text list of usernames. Now each row
gets a colored circle icon + name + "(you)" for the authed user, and
every OTHER user's row carries View JSON / Fork buttons mirroring the
OtherUsersDraftsModal. For draft-only entries owned solely by the
authed user, the popover ends with "Only you can see this {kind}" so
the row's privacy is obvious. ScriptRow / FlowRow / AppRow thread
workspace + itemKind + path + editPathFor through; AppRow switches
between app / raw_app on app.raw_app.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* nit
* fix(drafts): clone only the forker's per-user drafts on workspace fork
clone_drafts copied every user's drafts, but only the forker gets added
to the fork's usr table. Drafts owned by absent users LEFT-JOIN to NULL
in the home page's draft_users aggregate, surfacing as multiple
legacy-style rows at one path and crashing the popover with
each_key_duplicate. Filter the clone to email = forker OR email IS NULL,
and key the popover's #each by index defensively so future legacy
collisions can't crash the page either.
Also re-adds `draft_only: None` to NewScript/CreateFlowBody literals in
tests — the auto-generated windmill-api-client still carries the field
and the previous commit dropped them too aggressively.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): always populate other_drafts_users in maybe_overlay_draft
Reset-to-deployed reloads the deployed payload with get_draft=false,
which made the backend return other_drafts_users=[]. The route then
reassigned otherDraftsUsers to the empty list, dropping the count to
0 and hiding "See others' drafts" in the AutosaveIndicator popover —
but the other users' drafts hadn't actually gone anywhere. Fetch the
list independently of get_draft so the popover stays accurate across
reset reloads.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(drafts): alert user when their draft is older than the latest deploy
Open a modal on editor mount when the per-user draft was saved before
the latest deploy at the same path — i.e. a teammate deployed a new
version while this user's draft was sitting. Two choices: discard the
stale draft and pick up the deploy, or keep editing the older draft.
DraftEditorModals computes the staleness from the timestamps each route
threads in (script.created_at, flow.edited_at, app_version.created_at)
and the "Load latest deploy" callback reuses the route's existing
reset-to-deployed logic. Wired for script / flow / app / raw_app
editors; trigger / resource / variable drawer editors follow a
different pattern and aren't covered here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): deploy only wipes the deployer's draft, not everyone else's
Script / flow / app deploys ran an unconditional DELETE on every draft
at the path, so a teammate's deploy silently destroyed any other
user's pending draft. After the wipe, the other user's tab kept
auto-saving — re-creating the row at a NOW timestamp newer than the
deploy — and StaleDraftModal never fired because draft_saved_at had
been bumped past the deploy. Filter the DELETE to email = deployer
(plus the legacy NULL row), so other users' drafts persist and the
stale-draft prompt actually fires on their next reload.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): surface save failures in AutosaveIndicator instead of pretending Saved
postSave caught network errors with `console.error` and let the runner
finish normally. The indicator read the saving → none transition as a
successful save and flashed "Saved" even when the request had thrown.
Track failed keys in a SvelteMap, expose `'failed'` as a new
UserDraftSyncState, render "Save failed" in red with a CloudOff icon.
Failure clears on the next successful save for the same key, or when
recordRemoteSync seeds a fresh authoritative timestamp.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): surface 'Save failed' inside the AutosaveIndicator popover too
The popover used to repeat the cheerful "All changes are saved as a
draft on the server..." copy even when the inline label said
"Save failed", which read as contradictory. Add a red, text-xs warning
at the top of the popover body when the sync state is `failed`,
explaining that the latest edits didn't reach the server and that
editing again retries the save.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): surface the actual error message in the AutosaveIndicator popover
Replace the generic "your latest changes did not reach the server" copy
with the real failure detail. The syncer now stores the extracted
message in the failures map (formatSaveError walks body / message /
statusText) and exposes it via the state handle's `failureMessage`
getter. Popover renders it in red, monospaced, scrollable so a long
server traceback doesn't blow out the popover.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): suppress Saving/Saved indicator during a reset-to-deployed discard
A `value: null` POST is a discard, not a save, but it ran through the
same runner the indicator watched — so resetting to deployed flashed
"Saving..." → "Saved", reading as "your draft just landed" while we
were actually wiping it. Track in-flight discards in a SvelteSet,
expose a distinct `'discarding'` UserDraftSyncState, and the indicator
stays quiet for it: no spinner, no label change, and the
`discarding → none` transition deliberately skips the "Saved" flash.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Revert "fix(drafts): suppress Saving/Saved indicator during a reset-to-deployed discard"
This reverts commit 625a47c5d2.
* fix(drafts): flush pending autosaves when the editor hook unmounts
SPA navigation doesn't fire `pagehide`, so a debounced edit (up to
maxDebounceMs old) silently disappeared when the editor was unmounted
mid-typing. `UserDraft.useMany`'s onDestroy now walks every acquired
entry and fires `UserDraftDbSyncer.flush(query)` before releasing,
re-submitting the pending opts with `immediate: true`. The POST rides
the runner's own lifetime and survives the component teardown.
`use` / `useReactive` are thin wrappers around `useMany` so they
inherit the flush automatically. Editors that don't go through the
hook (sessions' `ScriptEditorView`, `AppJsonEditor`, copilot adapter,
DraftBadge fork action) only call `UserDraftDbSyncer.save` for
one-shot operations and don't need lifecycle flush.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* nit
* feat(ui): Modal2 fixedHeight='adaptive' sizes the modal to its content
The fixed-height steps force either wasted whitespace or clipped
content for small dialogs. `adaptive` emits no height rule (still
capped by max-h-screen-80) so the modal hugs its content. Use it in
StaleDraftModal, which only has two lines of copy and a button row.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(drafts): 'Create test drafts' button on the home page
Dev/QA helper that seeds one per-user draft for every supported kind
(script, flow, app, raw_app, trigger_schedule, resource, variable) at
fixed u/{me}/draft_<kind> paths, so the draft surfaces (home badges,
editors, stale-draft modal, others' drafts modal) can be exercised
without hand-creating items. Re-clicking overwrites the same paths.
Value shapes mirror what each editor's autosave writes, matching the
backend list synthesizers that parse them back.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): dedupe app list rows when a path holds both app and raw_app drafts
The apps list LEFT JOINed draft with typ IN ('app', 'raw_app') for the
is_draft flag — a path holding BOTH kinds for the same user (easy to
hit: open a raw-app draft path in the regular app editor and its
autosave writes the second kind) fanned the row out into two identical
entries and crashed the home list with each_key_duplicate. Join a
DISTINCT (path, workspace_id) subquery instead. Same dedup for the
draft-only synthesis block via DISTINCT ON (path) keeping the most
recently saved kind.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(drafts): asterisk on resource/variable/schedule/trigger rows with own draft
Add an is_draft flag to ListableVariable / ListableResource /
ScheduleLight / BaseTrigger list rows — a scalar EXISTS subquery on the
draft table for the authed email (no join, so no row fan-out), plus
is_draft: true on the synthesized draft-only rows. The list pages
(variables, resources, schedules, all trigger kinds) append `*` to the
displayed name when set, mirroring the home page's convention.
Also fixes draft-only resources never appearing on the resources page:
the page always lists with resource_type_exclude=cache,state,app_theme
(its tab split) and the synthesis gate bailed on any type filter. The
gate now keeps synthesizing and applies resource_type /
resource_type_exclude per-row against the draft JSON instead.
list_triggers (trait default) takes an authed_email: Option<&str> —
Some from the list endpoint, None from workspace export.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Revert "feat(drafts): 'Create test drafts' button on the home page"
This reverts commit 1f244a2a8b.
* fix(drafts): P1 hardening — save authz, secret scrubbing, hot-path index
1. save_draft had no authorization check (a regression from the old
create_draft's require_writer_of_path): any workspace member could
plant drafts in another user's u/ namespace or unwritable folders,
and those drafts get surfaced to every reader of the path (home
circles, others'-drafts modal, View JSON / Fork). New
require_can_write_path: admins; own u/ namespace; g/ namespace when
in the group; f/ folders with the write/owner bit (with the same
folder-claim refresh deploy endpoints use). Operators are rejected
outright — they're excluded from every other draft surface.
2. Secret variable values were persisted in the draft table in
plaintext. save_draft now blanks variable.value for is_secret drafts
at write time (the editor never round-trips secret values anyway —
it fetches with decrypt_secret=false), and a migration scrubs rows
persisted before the guard.
3. fetch_other_drafts_users runs on every get-by-path request with
(workspace_id, path, typ) and no email predicate — neither partial
unique index covers it, so it seq-scanned a table that accumulates
per-user autosaves across all workspaces. Add a plain btree index;
it also serves get_draft_for_user's IS NOT DISTINCT FROM lookup.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): Ctrl/Cmd+S flush narrates via the indicator, not a toast
The "Draft saved" toast fired even with the network down — flush never
rejects (postSave catches errors internally and routes them to the
failures map), so the success branch always ran. Drop the toasts from
the script / flow / raw-app Ctrl+S handlers; the AutosaveIndicator
already narrates the flush truthfully (Saving... → Saved / Save failed
in red).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): Ctrl/Cmd+S always flashes Saved in the indicator
After dropping the toast, an explicit Ctrl/Cmd+S with nothing pending
(the common case — autosave already landed everything) gave zero
feedback: flush() no-ops when pendingSaveOpts is empty and no state
transition fires. flush() now bumps a reactive per-key counter on
completion (no-op path included), exposed as flushCount on the state
handle; the AutosaveIndicator flashes "Saved" on the bump when the
pipeline is idle. Real flushes keep narrating through Saving... →
Saved / Save failed as before.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ui(drafts): Ctrl/Cmd+S replays the green backdrop flash on the indicator
Decouple the one-shot light-green → transparent backdrop from the load
hint label: triggerFlash() owns the keyed span (mounted only while the
animation runs), and both the on-mount hints and the Ctrl/Cmd+S
confirmation route through it. The flush bump fires after the POST
lands, so a real flush flashes too — not just the no-op path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(drafts): 'Create test drafts' button on the home page
Dev/QA helper that seeds one per-user draft for every supported kind
(script, flow, app, raw_app, trigger_schedule, resource, variable) at
fixed u/{me}/draft_<kind> paths, so the draft surfaces (home badges,
editors, stale-draft modal, others' drafts modal) can be exercised
without hand-creating items. Re-clicking overwrites the same paths.
Value shapes mirror what each editor's autosave writes, matching the
backend list synthesizers that parse them back.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): Ctrl/Cmd+S reaches the raw-app flush from every editor surface
The raw-app window keydown handler never fired in practice: the file
editor is a VS Code workbench in a same-origin iframe (keydowns don't
cross documents) and the inline-script / YAML Monacos swallow Ctrl+S
via addCommand. Two hooks:
- attach a capture-phase keydown listener inside the iframe document on
each load (no preventDefault — VS Code's own save still runs, we
flush the pending autosave alongside it);
- Editor.svelte / SimpleEditor.svelte re-broadcast their swallowed
Ctrl+S as a `wm-monaco-save-shortcut` window event, which
RawAppEditor listens for.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): editing a draft-only item opens create mode prefilled from the draft
Variable / resource / schedule / trigger editors treated every loaded
path as deployed and routed saves through the update endpoints, which
404 for draft-only items ("Resource not found at name ..."). The
get-by-path responses already mark the case (`no_deployed` from
fetch_draft_only) — editors now flip to create mode when it's set:
- VariableEditor / ResourceEditor: existedInitially = !no_deployed
- ScheduleEditorInner + all 10 trigger editor inners: loadTrigger /
loadSchedule return { overlay, noDeployed } and openEdit sets
edit = !noDeployed
The form opens prefilled from the draft and deploys via create, whose
endpoints already delete the creator's draft on success.
(The "Could not load schedule: Not Found" half of the report was a
stale dev backend — getSchedule?get_draft=true verified working on the
current build.)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): leading-edge draft saves for raw apps (no double debounce)
Raw-app file changes reach the parent already coalesced — the UI
Builder iframe holds a ~1s trailing debounce on its rebuild and only
posts setFiles when it fires. The syncer then stacked its own 1.5s
trailing window on top, so the draft landed ~2.5s after the user
stopped typing. The debouncer now supports a leading edge (run
immediately when the key is idle and cooled down; later schedules in
the window coalesce trailing with the max-wait ceiling, mirroring the
classic editor's first-keystroke-materializes-immediately logic), and
raw_app saves opt into it. The app build keeps its own trailing
debounce inside the iframe — only draft persistence is affected.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ui(drafts): blue flash for load hints, green for save confirmations
The backdrop flash now carries meaning: green = "your save landed"
(Ctrl/Cmd+S), blue = informational on-mount hints ("Loaded from
draft", "Others are working on this ..."). Color is passed as an
inline CSS custom property the keyframe reads, so the single keyframe
serves both variants.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Revert "fix(drafts): leading-edge draft saves for raw apps (no double debounce)"
This reverts commit 1b996fd73a.
* feat(drafts): 'Enable auto-save' toggle in the AutosaveIndicator popover
Browser-wide preference (default on, persisted in localStorage). While
off, the reactive keystroke mirror never POSTs — saves marked
`auto: true` park their latest opts in pendingSaveOpts instead of
scheduling, and the unload keepalive flush is skipped, so nothing
leaves the tab except explicit actions: Ctrl/Cmd+S flush (sends the
parked latest content), discard / reset-to-deployed, fork, conflict
overwrite. The indicator shows a muted cloud-off while disabled (the
idle check-mark would otherwise read as "everything saved") and the
popover copy explains the Ctrl/Cmd+S-only behavior. Re-enabling
re-schedules every parked unsaved draft so edits made while off catch
up immediately.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Revert "feat(drafts): 'Create test drafts' button on the home page"
This reverts commit fd7013b399.
* feat(drafts): Review & Deploy covers variables/resources/schedules/triggers
The drafts review page only assembled scripts/flows/apps from three
paginated list endpoints, so drafts of every other kind were invisible.
New GET /w/{ws}/drafts/list returns every draft of the authed user in
one query over the draft table, with a per-kind draft_only flag
(deployed-table EXISTS per kind); getDraftItems switches to it, which
also drops the 3×N-page fan-out.
CompareDrafts renders the new kinds (icon via a UserDraftItemKind →
layout-Kind mapping, gray kind badge, list-page edit links for
drawer-based editors), diffs them through a generic overlay GET, and
deploys them by replaying the editor save: create/update for variables
and resources, saveScheduleFromCfg for schedules, the per-kind
save*TriggerFromCfg helpers for the ten standalone trigger kinds.
Also fixes two paths stale since the draft_only column removal:
draft-only flows/apps now deploy via create (update 404s — there is no
row anymore), and discard always deletes the draft row (the old
delete-the-item branch 404'd for the same reason).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(drafts): optimistic asterisk while editing in list-page drawers
The `*` suffix on variable/resource/schedule/trigger rows came from the
server's is_draft flag, which only updates on a refetch — editing an
item in the drawer didn't mark its row until much later. New
localDraftHints module (SvelteSet-backed): editors publish their dirty
state (the same condition that shows the "You have unsaved changes"
banner) and the 13 list pages OR the hint into the asterisk condition,
so the suffix appears the moment the form diverges and clears on
discard/teardown. Wired once in useTriggerDraftSync (covers the
schedule editor and all ten trigger editors) plus VariableEditor and
ResourceEditor.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ee repo
* fix(drafts): draft hints persist past editor teardown, re-sync on reopen
Clearing the optimistic asterisk on drawer close was wrong: the
divergence the editor observed is autosaved server-side, so the draft
outlives the drawer and the asterisk should too. Hints are now
corrected rather than expired — while an editor is settled on an item
it publishes the observed truth in both directions (divergence sets,
sitting at the deployed baseline clears), so a draft discarded from
another tab loses its stale asterisk the next time the item is opened.
No teardown cleanup anywhere.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(drafts): list-page asterisk mirrors the editor's banner, not stale is_draft
The asterisk was `is_draft || hint` — an OR can turn the asterisk on
optimistically but can never turn it OFF, so after discarding a draft
(or editing back to the deployed value) the stale server flag kept the
asterisk until the next list refetch.
Make the local hint a tri-state override instead: the editor publishes
the live banner state (true/false) into a SvelteMap, and the list pages
read `getLocalDraftHint(...) ?? is_draft` — the editor's observed truth
wins over the stale server flag in both directions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): autosaves equal to the deployed value delete the draft instead
When the user edits back to exactly the deployed value, the reactive
autosave mirror used to persist a baseline-equal copy — a useless draft
row that kept `is_draft` (and the list asterisk) on after refetch.
Add a `discardIfEqualTo` baseline getter to `UserDraft.useMany` specs:
when the cell's value deep-equals the deployed baseline, the mirror
POSTs `value: null` (delete) instead of the value. The variable and
resource editors pass their `initialStates` baseline, guarded on
`existedInitially` — draft-only/new items have no deployed copy, so
equality must never delete their only data.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Draft encryption for secret variables
* fix(drafts): discardIf predicate + deploys clear the asterisk and draft row
Two follow-ups on the baseline-equal-autosave-deletes change:
1. `discardIfEqualTo` (baseline getter + raw deepEqual) becomes
`discardIf` (predicate). Raw deepEqual reported spurious diffs after
a refresh: drafts round-trip through JSON, which strips
undefined-valued keys, so a restored draft (`{}`) never compared
equal to the freshly built baseline (`{ labels: undefined }`) and
the delete never fired. The editors now pass the SAME comparison
that drives their "unsaved changes" banner — a new exported
`draftValuesEqual` (JSON-normalized deep equality) used by both —
so the banner and the synced draft can never disagree.
2. Truly saving (deploying) clears the asterisk and the draft row:
- variable/resource editors: replace post-deploy `UserDraft.remove`
(blanks the cell to `undefined`, which reads as dirty and keeps
the banner + asterisk on) with `discard` to the just-saved state,
and refresh `initialStates`/`existedInitially` so the editor
settles clean.
- trigger editors: `useTriggerDraftSync.discard` publishes the hint
off explicitly — after a deploy the editor's `deployed()` baseline
is stale, so the hint effect alone would keep the asterisk on.
- Review & Deploy page: `deployDraft`/`discardDraft` clear the hint.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* revert encryption just for the resources part
* fix(drafts): required const DRAFT_KIND on TriggerCrud; deploy/delete cover raw_app
The TriggerCrud::user_draft_item_kind() default matched on TRIGGER_TYPE
and panic!'d on any unmapped string — a runtime crash on the first draft
save for a trigger that forgot to map. Replace it with a required
associated const DRAFT_KIND, so a missing mapping is a compile error.
user_draft_item_kind() now just returns Self::DRAFT_KIND; every impl
(OSS + EE) declares the const.
Also fix the app deploy/delete draft cleanup to cover raw_app: raw apps
deploy and delete through the same internal path, but the cleanup
filtered typ = 'app' only, leaving raw_app drafts dangling
(create_app_internal apps.rs:1465, update path apps.rs:2077) or
un-archived on delete (apps.rs:1687).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): deleting an item wipes every user's draft, not just the caller's
Scripts/flows/apps already wiped all users' drafts on delete, but
resources/variables/schedules/triggers called delete_user_draft
(caller-scoped), so a teammate's draft on the just-deleted item lived on
forever — surfacing through fetch_other_drafts_users with no item left
to deploy onto. Add delete_all_drafts_for_path (all emails + the legacy
NULL row) and use it in every delete handler; keep delete_user_draft for
the discard-my-own-draft flow where the item lives on.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* perf(drafts): skip other-drafts query on non-editor reads (get_draft=false)
maybe_overlay_draft ran fetch_other_drafts_users (a usr join) on every
get-by-path, including worker/CLI reads of MB-scale flows & apps that
pass get_draft=false and never render the draft overlay or "others
editing" surfaces. Gate the query behind get_draft — only editor reads
pay for it. Reset-to-deployed editor reloads still get it (they pass
get_draft=true).
(Eliminating the serde_json::to_value materialization of the deployed
payload needs WithDraftOverlay to become generic over T, which is folded
into the get-by-path choreography refactor.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(drafts): single-source the kind→table mapping via deployed_table()
The kind→table dispatch lived in three places that could drift: the
TriggerCrud string-match (already replaced by const DRAFT_KIND), the
table_for_kind access-check map, and a hand-written draft_only CASE in
list_drafts.
Add UserDraftItemKind::deployed_table() as the single source (plus an
ALL enumerator). table_for_kind now delegates to it, and the list_drafts
draft_only CASE is generated from it at runtime (table names come from
the closed enum, never user input — no injection). Drift between the
access check and the existence check is now impossible by construction.
Webhook and the native triggers (poll/cli/nextcloud/google/github) map
to None: they have no path-keyed backing table and aren't draftable, so
they report draft_only=true and use a path-only access check. This also
fixes a latent bug where table_for_kind mapped native kinds to
native_trigger, which has no `path` column — the access query
`SELECT 1 FROM native_trigger WHERE path = $1` would have errored.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ee repo
* fix(drafts): close variable draft-secret laundering oracle (sentinel + rehydrate)
save_draft encrypts secret variable values with the workspace key, but
the ciphertext was round-tripped to the client and the deploy endpoints
decrypted whatever $encrypted: ciphertext the client submitted
(variables.rs create/update). Any workspace member who can write a
variable path could take an arbitrary workspace-key ciphertext (another
user's secret draft via GET /drafts/get with only path-read, or a
deployed secret's stored value) and submit it as their own secret
variable's value — the server decrypted it and, since they own the path,
they read the plaintext back. That bypasses the audited decrypt_secret
permission.
Fix: the ciphertext never leaves the server. get_variable swaps a draft
secret's $encrypted: value for an opaque $draft_secret sentinel (both the
draft overlay and the draft-only inner stand-in). On deploy the client
sends the sentinel back and the server rehydrates the plaintext from the
caller's OWN draft row — the only ciphertext it ever decrypts is one it
encrypted for this exact (workspace, path, email). A raw $encrypted:
submitted by a client is now rejected outright.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): don't clobber a secret draft when autosaving the $draft_secret sentinel
After reload the client holds the $draft_secret sentinel for a secret
variable (never the ciphertext). Editing some OTHER field (description,
labels) triggers an autosave carrying value="$draft_secret" — and
save_draft's encrypt_secret_variable_value, seeing a non-empty,
non-$encrypted: string, encrypted the literal sentinel, overwriting the
real ciphertext in the draft row and losing the secret.
Treat the sentinel as "secret unchanged": restore the $encrypted:
ciphertext already stored in this user's draft row instead of encrypting
the placeholder (falling back to empty only if there's no prior
ciphertext). The new lookup reuses the same query shape as the deploy-
time rehydrate, so no new offline cache entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Revert "$draft_secret" sentinel approach for variable draft secrets
Reverts 339c259fce and b2c38ef407. Instead of round-tripping a sentinel
and rehydrating server-side, we close the laundering vector more simply
by disabling cross-user draft visibility for triggers/resources/variables
(next commit) — an attacker can no longer read another user's secret
draft ciphertext to launder it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(drafts): keep drafts private to their owner for resource/variable/trigger kinds
Replaces the reverted $draft_secret sentinel: instead of laundering-proofing
the ciphertext round-trip, simply don't expose other users' drafts for the
drawer kinds (resource/variable/triggers). A viewer can no longer obtain
another user's secret-variable draft ciphertext, so it can't be laundered
into plaintext via deploy.
UserDraftItemKind::shares_drafts_across_users() — true only for
script/flow/app/raw_app. maybe_overlay_draft skips other_drafts_users for
non-sharing kinds, and get_draft_for_user (View JSON / Fork) returns 404
for them. Own-draft load/save is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(drafts): make the list-page asterisk hint a shadow of UserDraftDbSyncer
The optimistic `*` hint was written by three open-editor publishers, so
draft deletions that didn't go through an editor (banner discard,
autosave-back-to-baseline, Review & Deploy) left a stale asterisk that a
server refetch couldn't clear (the hint overrides is_draft).
Move ownership to the syncer — the one choke point where a draft's
existence actually changes:
- postSave sets the hint on a saved write (value !== null) and clears it
on a delete (null), so every syncer-routed delete clears it for free.
- save() lights it optimistically when a real save is scheduled, so the
asterisk still tracks the editor's banner without the debounce lag.
The editors no longer SET the hint; they only CLEAR it when settled at
the deployed baseline (so a draft discarded from another tab disappears
on reopen). discardDraft drops its explicit clear (postSave covers it);
deployDraft keeps one (it deletes server-side, bypassing the syncer).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(migrations): fold draft index + secret scrub into the base sync migration
Merge 20260610095349_draft_workspace_path_typ_index and
20260610100018_scrub_secret_variable_drafts into the base
20260528143710_draft_user_sync_schema migration (the index creation +
secret-draft scrub in .up, the index drop in .down; the scrub stays
irreversible). 20260609165313_remove_draft_only remains standalone.
Verified the full chain applies and reverts cleanly on a fresh DB.
(Rewrites an already-applied migration — existing dev DBs need a reset.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(drafts): promote the get-by-path draft choreography to one helper
The "Some(deployed) → overlay / None+get_draft → draft-only / None → 404"
dance was copy-pasted across the get-by-path handlers and had drifted
(different 404 text, the trigger one missing the draft-only fallback at
first). Promote it to windmill_common::overlay_or_draft_only<T>, which
takes the deployed entity as Option<T> and a per-route not_found closure.
Converts scripts, flows, apps, schedules, and triggers onto it. Resources
keeps its own (it runs an async explain_resource_perm_error on the 404
path) and variables keeps its own (secret-decrypt logic interleaved with
the draft fetch) — both genuinely diverge from the common shape.
(The serde_json::to_value elimination via a generic WithDraftOverlay<T>,
and the list-only draft synthesis dedup, remain as follow-ups.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* perf(drafts): serialize the deployed overlay payload in one pass
maybe_overlay_draft materialized the deployed entity into a
serde_json::Value tree (serde_json::to_value) and then serialized that
tree again into the response — two passes plus a full Value allocation
over what can be an MB-scale flow or app, on every get-by-path
(including get_draft=false worker/CLI reads).
Hold WithDraftOverlay.inner as a boxed erased_serde::Serialize trait
object instead, so the deployed payload flattens straight into the
response in one pass. The struct stays non-generic, so the helper and
all seven handler return types are unchanged; only the deployed type now
needs Send + 'static (already true — they're owned rows; added 'static
to TriggerCrud::Trigger to say so).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(drafts): one helper for the draft-only list synthesis query
The "draft rows at paths with no deployed counterpart" query was
copy-pasted into the variable / resource / schedule / trigger list
handlers, each hardcoding its own typ literal and NOT EXISTS table — a
drift hazard. Promote it to windmill_common::fetch_draft_only_list_rows,
which derives the absence-check table from kind.deployed_table() (the
same single source as the access check and draft_only flag). Each
handler keeps its own include_draft_only gating and per-type row mapping
(genuinely entity-specific); only the shared SQL is deduped. The trigger
handler's prior generated-SQL version is folded in too.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): route raw-app draft deploys through the raw-app endpoint [P1]
deployDraft's raw-app guard was `kind === 'app' && rawApp`, but Review &
Deploy passes `kind === 'raw_app'` (raw apps are their own DRAFT_KIND),
so the guard never fired and the row fell into the visual-app branch.
There `d.value` is undefined (a RawAppDraft has files/runnables/data, no
`value`), so AppService.updateApp did a partial update — resetting policy
to the publisher default, never bundling/deploying the files — the
backend then deleted the user's raw_app draft rows, and the UI reported
"deployed". The work-in-progress was destroyed without ever deploying.
Route `kind === 'raw_app'` (or the editor's `app` + rawApp) through
deployRawAppDraft. The now-unreachable `raw_app` arm of the visual-app
branch is dropped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): allow draft saves for item-level extra_perms writers [P1]
require_can_write_path only accepted namespace rules (own u/, member g/,
writable f/), dropping the item-level extra_perms check the old
create_draft had. A user granted write on e.g. u/alice/script via the
Share dialog could still deploy it (the update endpoints go through RLS)
but could no longer save a draft — and because the editors autosave
continuously with no permission gate, editing a shared item produced a
persistent "Save failed: you don't have write permission" and Ctrl/Cmd+S
failures.
Add the item-level fallback: when a deployed row exists at the path,
check its extra_perms for a write grant (every deployed table has
extra_perms; the table comes from the closed deployed_table() mapping).
Draft-only items have no row and stay governed by the namespace rules.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): pass rawApp on get-app for never-deployed raw-app drafts [P2]
A raw app that has only ever been drafted has no `app` row, so get_app
resolves the draft kind from the `rawApp` query param. getDraftDiffValues
("Show diff") and deployRawAppDraft both fetched with getDraft=true but
without rawApp, so the backend looked up the visual-app draft kind, found
nothing, and 404'd. Pass rawApp so the raw_app draft is found.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(drafts): surface the localStorage→DB migration with toasts
migrateUserDraftsToDb already uploaded legacy "userdraft/..." entries and
cleared them on success (and runs after the v1→userdraft normalizer).
Add the user-facing surface: when real legacy entries are detected, show
an info toast "Migrating local storage drafts ..."; on a per-draft
failure show an error toast "Could not migrate draft <path> in workspace
<X>" with a "Delete draft" action that drops the stuck localStorage entry
(otherwise it retries every mount). Unparseable junk is still cleared
silently up front, so the toast only fires for genuine drafts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(drafts): cover the autosave pipeline's pure-logic utilities [P2]
The deleted draft tests left the new debouncer + coalescing runner — the
core of the autosave pipeline — with zero coverage. Add vitest suites
(16 cases) for debouncerByKey (debounce window, latest-task-wins,
maxDebounceMs ceiling under a trickle, fresh-chain-after-fire, cancel,
key independence) and coalescingRunner (immediate run when idle, coalesce
burst to in-flight + latest, displaced-task drop, submitAndWait
resolve/reject/displaced, cancel semantics, key independence).
Broader replacement (save_draft conflict semantics + the require_can_*
checks as backend integration tests) still outstanding.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(drafts): add UserDraft.seed — a one-shot baseline load that never POSTs
The page editors bracket their new-draft / deployed-baseline loads with
stopSync + restartSync so the programmatic write isn't synced as the
user's edit. Forgetting restartSync silently disables autosave for the
session — the footgun behind the three divergent resume strategies the
review flagged.
`UserDraft.seed(kind, path, value)` is the scoped alternative: it sets
the cell (all reactive readers update) and arms a single-shot
`seedNextWrite` flag the sync effect consumes — adopting the value as the
new baseline and skipping exactly that one POST, with no suspension to
resume. Additive: stopSync/restartSync are untouched and still used for
the writes that fan out across editor components (initContent cascades).
Foundation for converting the editor bootstraps off the bracket.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(drafts): extract usePageDraftSync; convert the scripts editor onto it
First step of unifying the four page editors' hand-rolled draft
orchestration (three divergent handle-ownership models + an
easy-to-forget recordRemoteSync). usePageDraftSync is the single model —
the page analogue of useTriggerDraftSync — owning the re-keyed autosave
handle, the live-editor-draft registry entry, recordRemoteSync (now a
method, not a per-page ritual), seedBaseline (via UserDraft.seed), and
draft removal.
The scripts editor is converted as the reference adoption: its inline
useReactive handle, live-editor-draft effect, recordRemoteSync, and the
two UserDraft.remove calls now go through draftSync. The new-draft
stopSync bracket stays (it spans ScriptBuilder's initContent cascade).
Verified in a real browser against the dev stack: load fires no spurious
save, a code edit triggers exactly one save_draft POST + a draft row,
and the draft persists across reload. Flows / apps_raw / apps conversions
follow.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(drafts): convert the flows editor onto usePageDraftSync
Replace the inline useReactive handle + UserDraftDbSyncer.recordRemoteSync
+ UserDraft.remove with draftSync. effectivePath is omitted — flows
register their live-editor-draft entry through FlowBuilder
(liveEditorDraftStoragePath), so the composable doesn't double-register.
The new-draft stopSync + armRestartOnFirstInteraction bracket stays (it
spans FlowBuilder's seed cascade). flowStore reads/writes draftSync.draft.
Verified in a real browser: load fires no spurious save, a summary edit
triggers exactly one save_draft POST + a draft row, and the edit persists
across reload.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(drafts): convert the apps_raw editor onto usePageDraftSync
Replace the UserDraft.use handle + mirror, UserDraftDbSyncer.recordRemoteSync,
and UserDraft.remove with draftSync. `path` is a mount-scoped plain `let`
(the editor remounts per path), so the composable's useReactive re-keys
only on workspace change — equivalent to the prior capture-once use().
effectivePath omitted (RawAppEditor owns the live-editor-draft entry); the
new-draft stopSync + armRestartOnFirstInteraction bracket stays.
Type-checked and behavior-equivalent (handle mechanism unchanged; the
centralized recordRemoteSync/remove read the same `path`). Not
browser-exercised here — no existing raw app in the dev workspace and the
new-draft template-picker flow isn't scriptable quickly; scripts and flows
(same composable) were verified live.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): remove app autosave at its canonical key after deploy/rename
AppEditor keys the app autosave on the URL draft path and passes it down
as userDraftPath, but AppEditorHeader's post-deploy cleanup re-derived
the key from the just-typed deploy path (createApp) / the live $appPath
(updateApp) instead. For a new app the autosave lives at
u/{user}/draft_{uuid} while the typed path is the user's chosen name, and
a rename leaves the autosave at the original key — so removing at
path/$appPath missed the real draft row and orphaned it. Use the
canonical userDraftPath AppEditor already provides.
This is the "children re-derive the UserDraft key" fragility from the
review, addressed without giving apps a page-level handle — apps
deliberately lets AppEditor own the handle so the entry is destroyed on
unmount (a page handle would keep it alive and reintroduce spurious
autosaves on every /edit visit).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(drafts): integration tests for save_draft conflict semantics + authz [P2]
Replaces the deleted drafts.rs (which targeted the removed /drafts/create
API) with tests for the new surface:
- save_draft upsert → stale-last_sync conflict (rejected, value unchanged)
→ force overwrite → delete, the optimistic-concurrency contract.
- require_can_write_path: own namespace allowed, another user's namespace
rejected, operators rejected.
- the item-level extra_perms fallback — a user granted write on a deployed
item can save a draft on it (regression test for the authz drop).
- cross-user draft privacy: GET /drafts/get is 404 for the drawer kinds
(variable/resource/triggers), not blocked for script/flow/app.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(sqlx): refresh offline cache after the main merge
The merge auto-combined both branches' additions inside the resource
get-by-path query_as! (our draft_only/is_draft columns + main's
folder_labels(...) inherited_labels), producing query text neither branch
had cached — so the offline build failed for it. Regenerate the entry
(rename to the new content hash) and refresh a re-described workspace
query. Feature-gated/EE entries the local prepare can't compile are left
as committed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ee repo ref
* chore(system_prompts): regenerate for draft_only/is_draft trigger schema fields
The openapi.yaml trigger/schedule schemas gained draft_only + is_draft,
but system_prompts/generate.py wasn't rerun, failing the freshness check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(drafts): defer save_draft write authz to RLS via a FOR UPDATE probe
require_can_write_path re-implemented the item-level extra_perms write
rule in Rust (SELECT extra_perms + get_perm_in_extra_perms_for_authed) —
a third copy of rules whose canonical home is the RLS policies, and the
exact lane that regressed once already.
Replace it with an RLS write-probe: `SELECT 1 FROM {deployed_table}
WHERE path/workspace ... FOR UPDATE` through UserDB. Postgres applies
UPDATE policies to rows locked via FOR UPDATE, so a returned row means
the canonical policies (see_own / see_member / folder-write /
see_extra_perms_*_update / admin_policy) would let this user UPDATE the
row — no write rule re-implemented, no drift possible. The probe's row
lock is released by the immediate commit.
The claim-based namespace checks stay, evaluated FIRST: they read the
same JWT claims RLS does (so outcomes are identical), they spare the
autosave hot path a DB round-trip for the common own-namespace case, and
they are the entire check for draft-only paths — where no deployed row
exists, so there is structurally nothing for RLS to evaluate. The u/own
+ folder-owner part now goes through the shared
windmill_api_auth::require_owner_of_path instead of bespoke code.
Adds a read-only-grant test case (extra_perms value false): the row is
visible under the SELECT policy but FOR UPDATE filters it under the
UPDATE policy — pinning the semantics the probe relies on. All 4 draft
integration tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: point ee-repo-ref at the EE branch merge (has DRAFT_KIND consts)
ee-repo-ref was set to main's EE commit (d45b9a6) while the EE branch
was unpushed; building OSS (which requires const DRAFT_KIND on
TriggerCrud) against that EE ref fails with E0046 on every EE trigger
impl. The EE branch head e936e9a — the merge of d45b9a6 into the EE
remove-workspace-drafts branch, carrying the DRAFT_KIND consts — is now
pushed; point at it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): ignore permissioned_as fields in the unsaved-changes comparison
The schedule cfg carries permissioned_as / preserve_permissioned_as —
run-as deploy directives, not user-edited draft content — and the editor
round-trips them asymmetrically (preserve_… is rebuilt as
!!cfg.permissioned_as on load but `|| undefined` on build), so the
banner comparison could report a phantom diff.
Extract the normalization into a shared normalizeDraftForCompare (JSON
round-trip + a DRAFT_COMPARE_IGNORED_FIELDS list with the two fields)
and use it from BOTH comparators: draftValuesEqual (variable/resource
banner + discardIf) and useTriggerDraftSync's cfgDiffers (schedule and
trigger banners, the persist-effect's at-baseline discard, restore) —
one ignore-list, no way for the two to disagree.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* nit
* fix(drafts): at-baseline discard is auto-gated and only fires with a draft
Two related fixes to useTriggerDraftSync's persist-effect:
1. The reactive at-baseline discard bypassed the "Enable auto-save"
toggle: with autosave off, value saves were parked (correct) but the
discard's value:null still POSTed — so the editor never wrote drafts
yet kept reactively DELETING them, and the only network traffic was
discards. Thread `auto` through UserDraft.discard to the syncer; the
persist-effect passes auto:true (parked for Ctrl/Cmd+S when the
toggle is off), explicit discards (banner button, post-deploy
cleanup, reset-to-deployed) stay ungated.
2. The discard fired unconditionally whenever the form sat at the
deployed baseline — including a spurious value:null POST on every
drawer open. Guard on cfgDiffers(h.draft, deployed): undefined on a
fresh open (nothing to discard) and equal to deployed right after a
discard (no repeat per cfg recompute).
Verified live as a non-admin user on a schedule: toggle on → no POST on
open, edit → one value save, revert → one discard; toggle off → zero
POSTs (everything parked), banner still functional.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(drafts): scope the "Enable auto-save" toggle to the page editors
Add a canBeDisabled opt (default false) to UserDraft.use / useReactive /
useMany specs, threaded through acquireEntry into the reactive mirror's
save opts. The syncer's auto-save gate (and the pagehide-flush skip) now
only applies to saves whose handle opted in: the four full-page editors
— script / flow / raw app via usePageDraftSync, app via AppEditor's
use() — which are exactly the surfaces whose AutosaveIndicator carries
the toggle.
Drawer editors (variables / resources / schedules / triggers) keep the
default and always sync regardless of the toggle — previously a
toggle flipped off in some browser silently disabled their autosave and
the optimistic asterisk (both sit behind the same gate) with no toggle
UI anywhere on those surfaces to explain it.
Verified live: schedule edit with the toggle off now POSTs the value
save (and the discard on revert); script editor with the toggle off
still parks everything for Ctrl/Cmd+S.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): consume the import handoff stores in the new-draft bootstrap
The /add pages used to read importStore / importFlowStore /
importScriptStore / sessionStorage rawAppImport to seed the editor from
"Import from YAML/JSON", "Build app" (from a script/flow), and the
workflows-as-code import. Since /add became a pure redirect to
/{kind}/edit/u/{user}/draft_{uuid}?new_draft=true, the writers kept
firing but nothing consumed the payload — every import landed in an
empty editor.
Consume them (one-shot read + clear) in the four edit pages' new_draft
branches, layering the imported content over the empty template with
path kept '' so the friendly-name generation still runs:
- scripts: $importScriptStore spread over the empty script (non-empty
content also keeps ScriptBuilder's template bootstrap from overwriting
it — that cascade is gated on content == '').
- flows: $importFlowStore spread over the empty flow.
- apps: $importStore — wrapped exports ({summary, value, policy}) and
bare App values, mirroring main's /add.
- raw apps: $importStore then sessionStorage rawAppImport (the full page
reload for cross-origin isolation drops in-memory stores); honored
only when the payload carries files (rendering gates on them),
skipping the framework picker; otherwise the template seed.
Verified live: "Build app" from a script lands on /apps/edit with the
canvas seeded from the script instead of an empty editor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(drafts): remove dead delete_user_draft + its stale doc [C4]
The doc claimed item delete handlers call it, but those all moved to
delete_all_drafts_for_path (an item delete is for everyone); the
caller-scoped discard goes through the save_draft route with value:null.
That left delete_user_draft with zero callers (OSS and EE) — remove it
and its orphaned sqlx cache entry, and reword the contrast note on
delete_all_drafts_for_path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(migrations): retire the sync_drafts-era index comment + right-size it [C6]
The draft_user_sync_idx comment described the deleted sync_drafts
polling endpoint (editors polling created_at ranges every 2-10s) — that
design was replaced by recordRemoteSync + save_draft last_sync, and
nothing range-scans draft.created_at anymore. Since this migration only
exists on this branch, fix it before it ships: the index's real consumer
is GET /drafts/list (workspace_id + email equality, ORDER BY path), so
swap the vestigial trailing created_at for path (rows come back in
output order) and rename to draft_user_listing_idx. Chain re-verified
on a fresh DB. (Byte-for-byte migration edit — dev DBs that already
applied it need a reset, as with the earlier consolidation.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): discardDraft awaits the delete POST before refetching [I5]
UserDraftDbSyncer.save resolves at enqueue time for debounced saves, so
discardDraft's await finished ~1.5s before the value:null POST and the
invalidateWorkspaceDrafts refetch re-listed the just-discarded draft.
Use immediate: true (resolves after the POST lands), matching every
sibling delete-then-refetch path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): replace stale draft_only gates in the builders [I6]
draft_only was dropped from the get-by-path wire shape (the column is
gone; overlays carry no_deployed instead), so these four reads were
always undefined:
- ScriptBuilder "Exit & See details" gate and TriggersEditor's
isDeployed treated every draft-only script as deployed → now keyed on
savedScript.no_deployed like the sibling reads right next to them.
- FlowBuilder's deploy path never took the direct-save branch for
draft-only flows (no deployed version exists to compare against), and
"Exit & see details" was offered for draft-only flows (404 details
page) → both now keyed on the newFlow prop (driven by no_deployed),
which the rest of the file already uses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): Ctrl/Cmd+S flushes the draft in the low-code app editor [I7]
The app editor's keydown handler swallowed the shortcut with a bare
preventDefault() — every other page editor flushes the pending autosave
(UserDraftDbSyncer.flush) so the AutosaveIndicator narrates Saving... →
Saved and parked edits (autosave toggle off) actually persist. Wire the
same flush, skipped in the AI session pane where no UserDraft handle
exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(drafts): AI tool strings no longer describe drafts as localStorage [C2]
The copilot tool results/messages still told the model drafts were
"saved to local storage" / "a browser-only local draft" — drafts are
per-user rows in the server-side draft table now. Misleading the model
about the storage medium produces wrong explanations to users (e.g.
"your draft will be lost if you clear your browser data"). Reword all
occurrences to "draft" / "per-user draft (saved server-side)".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(openapi): drop stale draft_only request props, fix OtherDraftUser, regen deref [D4][C5]
- The create-script (NewScript), createFlow, createApp and createAppRaw
request bodies still documented draft_only — the backend request
structs no longer read it, so an older CLI sending draft_only: true is
silently ignored and fully deploys. Remove the property from the spec
so generated clients can't offer it. (Response-side draft_only on the
Listable* rows stays — the list synthesis populates it.)
- UserDraftOverlay.other_drafts_users item schema declared email and a
required draft_saved_at; OtherDraftUser serializes only username
(nullable for the legacy row — emails never leave the server). Align
the schema. [C5]
- Regenerate openapi-deref.yaml/.json (served at runtime via
include_str!) — they still advertised getScriptByPathWithDraft and the
deleted draft surface, and now carry the drafts/save_draft routes.
Frontend gen client regenerated; check:fast clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sessions): stop session pane from clobbering server-side raw-app drafts [P1]
loadRawApp seeded the session runtime from result.value (the deployed
payload), ignoring the .draft pocket returned by the get-by-path overlay.
The subsequent UserDraft.save then POSTed deployed content with no
last_sync recorded, silently overwriting the user's server draft.
Now the no-draft branch consumes result.draft when present (matching the
flow/script branches) and records draft_saved_at via recordRemoteSync so
later session saves are conflict-checked instead of treated as fresh.
Also corrects the header and aiDraft-branch comments that claimed the
overlay merges drafts into top-level fields — it never does.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(rust-client): pass new get_draft arg to variable_api::get_variable
getVariable gained a GetDraft query parameter (per-user draft overlay),
so the generated client fn takes a sixth argument. Verified with the
same generate+check pipeline CI runs (rust-client/dev.nu --check).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* nit: Workspace fork mention
* fix(drafts): don't leak other_drafts_users on draft-only private kinds [P2]
fetch_draft_only built the other_drafts_users list unconditionally,
while the deployed-overlay path gates it on shares_drafts_across_users.
For the drawer kinds (resource/variable/triggers) drafts are private to
their owner, so a draft-only GET was the one route that still told a
viewer who else has a draft at the path. Apply the same kind gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(drafts): probe a single row in the RLS write-probe [P2]
The script table keeps one row per version at the same path, so the
FOR UPDATE probe locked the entire version history and serialized
against concurrent deploys. LIMIT 1 locks one row — any UPDATE-policy
visible row proves writability (same pattern as scripts.rs's
latest-version lock).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): consume the /add?param= seeding intents in new_draft branches [D2]
The /add routes' redirect preserves query params, but the edit pages'
new_draft branches only consumed the YAML/JSON import stores — every
other intent the old /add pages handled landed in a blank editor:
- scripts: ?hub= and ?template= forks (with locked language and a
`<source>_fork` path suggestion), ?wac=python|typescript (WAC editor
template + language), ?lang=, ?initial_args= (URL form), and the
base64-JSON #hash payload (run page "Fork", workspace_settings
handler-template buttons; WAC detection restored for imports too)
- flows: ?hub= (preprocessor placeholder replacement + env-variables
panel), ?template=/?template_id=, ?fork=true (fork_flow localStorage /
window.opener handoff), #state, ?tutorial=
- apps: ?hub= (fromHub inputs panel), ?template=/?template_id=,
?tutorial=
The redirect itself also dropped the URL hash — SvelteKit forbids
url.hash in load, so it forwards window.location.hash (correct for all
hash producers: they arrive as full page loads via window.open /
target=_blank).
Seeding priority and toasts mirror main's /add pages. Verified live:
hub/template/wac/hash/fork intents for scripts and flows, hub for apps
(dev hub returns empty payloads, code path confirmed via toast +
inputs panel); no autosave POSTs fire during seeding.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): LS→DB migration no longer clobbers fresher server drafts [P2]
The one-off localStorage migration POSTed every entry with force: true,
unconditionally overwriting whatever the user had since saved server-side
from another browser. It now passes the LS copy's lastWrittenAt as
last_sync (epoch 0 when absent), so the server's conflict rule arbitrates:
empty slot → insert; server draft fresher → conflict, LS copy dropped;
LS copy fresher → upload wins. Verified all three outcomes against the
live save_draft endpoint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(raw_apps): drop banned $bindable(default) on template picker open [P2]
`open = $bindable(false)` on an optional prop is the AGENTS.md-banned
pattern (the default masks the undefined state). The only caller always
binds a boolean, so `open` is now a required prop with a plain
`$bindable()`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): fork others' drafts via the import handoff, not an eager save
The Fork actions (OtherUsersDraftsModal + DraftBadge popover) saved the
fetched draft server-side immediately and navigated to the fork path,
which surfaced three problems: a server draft existed before the user
edited anything, the Path widget treated the slot as an existing item
("Only the owner can change the path"), and the value's draft_path kept
the source path while the URL said X_owner_fork.
Forking now routes through the same one-shot import handoff as the
"Import from YAML/JSON" actions (new shared forkDraftToImport helper):
stash the value in the kind's import store, navigate to /add, and let
the new_draft branch seed a brand-new own item — nothing saved until the
first real edit, fresh renamable path, no source identity riding along.
The editPathFor/currentUserUsername plumbing that only served the old
flow is removed from both fork surfaces and their callers. The new_draft
branches also clear the previous path's draft-presence state
(otherDraftsUsers, loadedFromDraft, stale-draft timestamps) — the page
component is reused across same-route navigation, so forking from an
editor with collaborators used to carry the "Others are working on
this" hint onto the fresh draft.
Verified live: fork of a legacy draft seeds content+summary on a fresh
u/{user}/draft_{uuid} slot with zero save_draft requests and no
leftover collaborator hints.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(drafts): replace deprecated Popover with meltComponents Popover
- Migrate from old Popover.svelte to meltComponents/Popover.svelte
- Convert to new trigger/content snippet pattern with openOnHover=true
- Maintain hover behavior with debounceDelay=100
- Add key to visibleUsers each block for Svelte 5 compliance
* feat(drafts): seed forked drafts with the source path in the forker's namespace
Forking u/admin/myflow as guest now seeds the Path widget with
u/guest/myflow instead of a random friendly name — everything after the
source path's first two segments is kept, so f/folder/my/flow becomes
u/guest/my/flow. The re-homed path travels from forkDraftToImport to the
new_draft branches as a ?seed_path= param (the redirect preserves query
params; plain ?path= would be eaten in transit by ScriptBuilder's legacy
collab-param cleanup, which deletes path/collab from the live
searchParams object).
The script editor also passes initialPathChosen for any seeded path —
MetadataGen fires onChange for a non-empty summary at mount, and the
summary→path auto-slug would otherwise overwrite the explicit seed
(hub/template forks and URL-hash payloads included).
Verified live: forking a draft on u/admin/hard_working_script seeds
path u/admin/hard_working_script (with the "path already used" warning),
keeps the drafted summary/content, and still fires no save_draft until
the first edit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): DiffDrawer "Restore deployed" actually discards the draft [P1]
All four restoreDeployed implementations POSTed the delete through the
debounced pipeline and reloaded with getDraft defaulting to true: the
reload's draft write re-entered the autosave mirror (the one-shot seed
guard was consumed on first load), and debouncerByKey displaced the
queued value:null with the new save — the delete never reached the
server and the editor re-rendered the draft it was told to discard.
They now funnel through runResetToDeployed (the stopSync-bracketed
delete the AutosaveIndicator reset already uses) with each page's
proven reset body (getDraft: false reload), so the suspension mutes the
mirror while the delete flushes and sync re-arms on first interaction.
Also fixes the raw-app drawer navigating to the visual app editor
(/apps/edit) instead of /apps_raw/edit [P2].
Verified live on the script editor: Restore deployed issues exactly one
save_draft ({value:null} answered status=saved), the server row is
gone, and the editor re-renders the deployed content.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): deploying a draft-only item reliably deletes its draft
Two bugs left the slot draft (u/{user}/draft_{uuid}) alive after a
successful deploy:
- RawAppEditorHeader.createApp removed the draft at the just-typed
deploy path instead of the URL slot key (the visual header documents
exactly this trap), orphaning the real row for every draft-only
raw-app deploy.
- Everywhere else the delete went through bare UserDraft.remove, which
only QUEUES the value:null in the per-key debouncer. Editors that stay
mounted through the post-deploy navigation (AppEditor, RawAppEditor —
and timing-dependently the script/flow builders' post-deploy
draft_triggers mirror) keep mirroring their working value, and one
such write displaces the queued delete with a fresh save — observed
live: deploying a new visual app re-saved the full grid value at the
slot right after deploy.
New discardDraftAfterDeploy helper (userDraftToast.ts) applies the same
bracket runResetToDeployed uses: stopSync to mute the mirror, remove +
immediate flush so the displacement window closes, re-arm on first
interaction. Wired into the script/flow pages' onDeploy and both app
headers' create/update paths (session-pane guards preserved).
Verified live for all three kinds: draft-only deploy issues the
value:null (status saved), the slot row is gone, and no post-deploy
save re-creates it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): forks-compare deploy clears drawer-kind drafts too
The script/flow/app deploy endpoints delete the deployer's draft
server-side, but the drawer kinds' (variable / resource / schedule /
triggers) create/update endpoints never touch the draft table — their
editors discard client-side after a save. deployDraft replayed the save
but not the discard, so "Deploy n drafts" on /forks/compare deployed
those kinds correctly and left the drafts listed forever.
deployDraft now issues the canonical value:null delete (immediate) for
the drawer kinds after a successful save. Verified live: deploying a
draft-only variable from /forks/compare creates the variable and the
draft row is gone.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): StaleDraftModal "Load latest deploy" actually discards the stale draft [P2]
The modal invoked onLoadLatestDeploy directly — the draft = undefined
write queued the delete and the reload's deployed-payload write
displaced it, overwriting the stale draft with a deployed-identical
copy (is_draft stuck on, asterisk persists, modal can't re-fire since
draft_saved_at moved past the deploy). All four pages now run the
callback through runResetToDeployed, same as the DiffDrawer restore.
Verified live: stale-draft scenario → Load latest deploy → exactly one
value:null POST, draft row gone, editor renders the newer deploy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): don't acquire a sync entry for empty-path specs [P2]
The read-only historical-hash view (/scripts/edit/x?hash=...) computes
draftPath '' but useMany still acquired a live entry at ws/script/ —
every edit mirror-POSTed to /drafts/save_draft/script/ (unroutable),
populating the failures map and pinning the AutosaveIndicator on "Save
failed" with a retry per debounce window. Empty-path specs now get a
detached local-only handle: bind: works, nothing syncs — which is what
usePageDraftSync's doc always claimed. Verified live: editing in the
hash view fires zero save_draft requests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): no spurious conflict after bfcache restore of a flushed page [P2]
flushOnPageHide advances the server rows with unreadable keepalive
POSTs and leaves lastSyncMap stale — correct when the document dies,
wrong when bfcache resurrects it: the next autosave carried the
pre-flush last_sync and the server rejected the user's own write as a
conflict, opening DraftSyncConflictModal. The flushed keys are now
remembered and dropped from lastSyncMap on pageshow with
event.persisted, so the first post-restore save takes first-push
semantics against this document's own flush.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ui(drafts): draft asterisk sits on the trigger row's main title
The draft hint rendered at the end of the secondary path line
(u/admin/item*) on the http/websocket/nats/kafka/email trigger lists —
easy to miss. It now renders at the end of the row's bold title, and on
the azure/gcp lists it moves from mid-title (after the path, before the
topic suffix) to the end of the line. mqtt/postgres/sqs/schedules
already had it on the title. Verified visually on the HTTP routes list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): trigger editors save the FIRST edit, not the second
Three interlocking fixes in the trigger autosave path:
- The entry's one-shot first-write seed guard (skipNextWrite) was never
consumed for trigger entries: the drawers don't write the cell on open
(the form holds the state, unlike variables/resources which pass a
defaultValue), so the guard stayed armed and silently swallowed the
user's FIRST edit — banner on, no asterisk, no save until a second
change. maybeRestore now seeds the cell with the post-load baseline
(server draft overlay if any, deployed otherwise) via UserDraft.seed,
consuming the guard without POSTing.
- Guard hygiene in the cell's sync effect: a programmatic write consumes
BOTH one-shot guards, and a no-op write (same serialization — e.g. the
trigger pages fire openEdit twice per row click, re-seeding the same
value) defuses a lingering seedNextWrite instead of leaving it armed
to eat the next real edit.
- The at-baseline auto-discard is now deferred + revalidated (600ms):
with the cell seeded, the double-openEdit churn transiently shows
form-at-deployed + cell-holds-draft and an immediate discard deleted
the server draft on open; the recheck skips the transient state while
a genuine user revert still discards.
Verified live on the HTTP route editor: open-with-draft restores the
draft with zero POSTs, the very first field edit saves, and reverting
the form to the deployed value deletes the server draft.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ui(drafts): underscore-separated uuids in draft slot paths
u/{user}/draft_{uuid} now uses underscores instead of dashes in the
uuid — path segments elsewhere in Windmill are [a-zA-Z0-9_] words and
downstream consumers treat '-' as a foreign character. Nothing parses
the uuid back, so existing dashed slots stay valid.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): cascade draft cleanup on bulk-delete and rename
Drafts have no SQL FK to their underlying items (only to password.email),
so deletion and rename must cascade programmatically. Two gaps remained:
- Bulk delete of variables/resources did not wipe per-user drafts at the
deleted paths (single delete already did via delete_all_drafts_for_path).
Cascade them — including the linked resource/variable rows the bulk
delete fans into — so no orphaned draft-only rows survive.
- Renaming a variable/resource/trigger left the per-user draft stranded at
the old path. Add delete_own_draft_for_path and clear the deployer's own
(+ legacy NULL) draft at the old path on rename, mirroring the
script/flow/app rename path; teammates keep theirs (StaleDraftModal).
Variable/resource renames also move the linked counterpart, so both
kinds' drafts at the old path are cleared. Schedules have no rename path.
Note: sqlx offline cache not yet regenerated for the new/changed queries.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): surface legacy NULL-email drafts and migrate pathless /add keys
Legacy workspace-scoped drafts (pre-per-user rows + the remove_draft_only
migration, all email IS NULL) stopped showing up because every per-user
lookup matched only email = self. Match (email = self OR email IS NULL)
everywhere a draft is surfaced or opened, with the owned row taking
precedence (DISTINCT ON / ORDER BY email NULLS LAST): the home drafts
list, the script/flow/app/drawer draft-only list syntheses, and the
get-by-path overlay/fallback.
The localStorage->DB migration also dropped pathless legacy keys
(userdraft/w/{ws}/{kind}/ with no path) — the new-item /add autosave —
because parseKey rejected an empty path, leaving them stranded in LS.
Mint a fresh u/{user}/draft_{uuid} slot for those (same convention as the
editors' /add redirects) so they migrate as regular draft-only items.
Note: sqlx offline cache not yet regenerated for the changed macros.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(sqlx): regenerate offline cache for draft cascade + legacy-draft queries
Adds the offline entries for the queries changed in the two preceding draft
fixes (delete_own_draft_for_path, the maybe_overlay_draft/fetch_draft_only
NULL-email fallback, and the script/flow/app draft-only syntheses).
Also forwards the `http_trigger` feature from windmill-api-openapi to
windmill-store: that crate imports `try_get_resource_from_db_as`
unconditionally, but the fn is cfg-gated behind a trigger feature, so the
openapi targets failed to compile in isolation (e.g. `--all-targets` under
resolver 2) — which blocked `cargo sqlx prepare`. The feature was already
present transitively in whole-workspace builds; this just makes it explicit
where the symbol is used.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): resolve own draft owner in the admins workspace
The draft-owner surfaces (home-page badge, "others' drafts", View JSON /
Fork) resolve a draft's email to a username via the `usr` table. The
`admins` workspace has no `usr` rows — there a user's "username" IS their
email — so the join missed every owner and returned NULL, which the badge
renders as "Legacy workspace draft". A user editing a deployed item in
`admins` thus saw their OWN draft plus the genuine legacy NULL-email row
both labelled "Legacy workspace draft" (the reported duplicate).
Add the identity fallback `COALESCE(u.username, CASE WHEN workspace_id =
'admins' THEN email END)` to the script/flow/app draft_users aggregations
and fetch_other_drafts_users, and accept username==email in
get_draft_for_user. The genuine legacy row keeps username NULL (its email
is NULL, so the CASE yields NULL too), so it alone reads "Legacy
workspace draft" while the user's own draft now reads "<email> (you)".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(drafts): keep "See others' drafts" after reset-to-deployed
other_drafts_users is only computed by the backend when getDraft is true
(the cross-user lookup is skipped otherwise). Reset-to-deployed reloads
with getDraft:false, so the editors were overwriting the known list with
the empty response — hiding the "See others' drafts" button until a full
page reload recomputed it. Discarding one's own draft is independent of
other users' drafts, which are untouched on the backend.
Only assign otherDraftsUsers on a getDraft:true load. Applied to the
script, flow, app and raw-app editors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* disable fork for operators
* Path reactivity issue
* docs(drafts): tighten draft-feature comments and drop dead code
The draft feature accumulated many multi-paragraph comments that risked
code-comment drift. Compact them to the AGENTS.md bar (constraints not
narration, state-once, no drafting-history), de-duplicating the repeated
draft_users / cascade / draft_only-synthesis rationale to one canonical
version per theme with terse cross-references elsewhere (~1300 fewer lines).
Also fixes three stale/contradictory comments surfaced while trimming:
- the operator authz note claimed operators are "excluded from every draft
surface", contradicting require_can_read_path (they can read some drafts,
never write) — reworded to match the code;
- a migration comment named a non-existent index (draft_user_sync_idx);
- a syncer comment documented the wrong map-key separator.
Removes notifyDraftLoaded (orphaned exported helper, no callers).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(drafts): rename save_draft route to /update for CRUD consistency
The draft write route was POST /drafts/save_draft/{kind}/{path}, which
stutters with the /drafts prefix and uses a non-house verb. Rename it to
POST /drafts/update/{kind}/{path} (operationId saveDraft -> updateDraft) to
match the codebase's CRUD convention (/list, /get/{path}, /update/{path}).
/list and /get/{kind}/{path} already matched and are unchanged.
Updates the handler, openapi spec + dereferenced bundles, the two
DraftService callers, the hand-built keepalive page-unload URL (it bypasses
the generated client, so it wouldn't be caught by regeneration), and the
integration tests. Response status values ("saved"/"conflict") are
unchanged, so there is no behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat: track real ai chat token usage and show context indicator
* fix: keep context anchor full-history accurate after trimmed sends
* nits
* feat: restyle context indicator and disable trim for unknown windows
* feat: hide context indicator below 50% usage when window is known
* fix: gate 1M claude context window to sonnet/opus 4.6+
* refactor: import context window helpers from modelConfig directly
* fix: keep base gpt-5 models at 400k context window
* fix: exclude date-suffixed claude 4 ids from 1M window gate
* refactor: replace context window heuristics with explicit model table
* fix: account for context overhead in trim loop stop condition
* fix: re-base context anchor when mode switch changes system prompt or tools
* refactor: replace context estimation with usage-report-driven compaction
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: chars/4 fallback for context usage when provider reports none
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: remove unused slide import failing svelte-check
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: re-seed context usage estimate on rewind so retry can compact
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: lazy read-side estimate fallback for context usage
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: fix reasoningRegistry mock to match resolveRequestReasoning
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
When a resource's type is not present in the workspace, ResourceForm falls
back to the raw JSON editor regardless of the "As JSON" toggle. The rawCode
seeding effect only ran when viewJsonSchema was true, so the editor stayed
empty and the resource value was not shown. Seed rawCode whenever the raw
editor is the active input (toggle on, or no schema-based form available).
Fixes WIN-2044
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The create-folder UI and API rejected hyphens in folder names, even though
hyphens are valid in paths elsewhere: owner/path validation already permits
them, and folders with hyphens can be created via the CLI or by deploying to
an `f/<folder>/...` path. This inconsistency blocked the common `folder-name`
convention (e.g. relative imports like `../folder-name/logic.ts`).
Allow `-` in the folder-name regex on both the backend (create endpoint) and
the frontend create form, and update the validation messages.
Fixes#8474
`POST /api/w/{workspace}/jobs/run_inline/preview` ran request-supplied
code inline (in-process, e.g. DuckDB) but was missing the operator
authorization guard that its sibling `/jobs/run/preview` enforces. An
authenticated operator — the most restricted role, which must not run
preview jobs — could execute arbitrary code in a single request
(file read/write, and OS command execution via the DuckDB `shellfs`
extension when worker egress is available).
This is the incomplete-fix residual of CVE-2026-22683 / GHSA-9q9g-rp9x-244h,
whose v1.615.0 patch covered the entity-CRUD endpoints but left this
direct inline-exec sink uncovered.
Add the same `is_operator` guard from `run_preview_script`. Audited the
rest of the preview/inline arbitrary-code endpoints (run_preview_script,
run_bundle_preview_script, run_preview_flow_job, the wait_result
wrappers, run_dynamic_select inline variant, dependency jobs) — all
already carry the guard. The `run_inline_script_by_path`/`by_hash`
endpoints run deployed scripts (operator-allowed, scope-checked) and
correctly remain ungated.
Fixes WIN-2043 (GHSA-pp5h-96x3-3wqq).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): enforce resource authz when loading MCP tools in agent worker
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: describe MCP authz test invariants instead of drafting history
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: require pinned sha for inline raw_code in viewer app run mode
* fix: gate rd_string import behind parquet feature to fix oss build
* fix: also require pin for raw_code with app_script id in viewer run mode
Folder labels are exposed verbatim as `inherited_labels` (via the
`folder_labels` SQL function) and rendered in keyed `{#each}` blocks that
throw Svelte's `each_key_duplicate` on a repeated key, crashing the list
views. The UI dedups labels on entry, but API / CLI / git-sync writes do
not, so a folder.yaml with `labels: [foo, foo]` persists duplicates.
- Dedup on write in create_folder and update_folder (order-preserving).
- Make folder_labels() dedup on read so it is resilient regardless of how a
row was populated, plus a one-time cleanup of already-persisted duplicates
so direct folder.labels reads (folder list, editor) are safe too.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): home tree view each_key_duplicate on folder/user name collision
The root keyed each in TreeViewRoot keyed groups by
`folderName ?? 'user__' + username`. A folder named `user__<name>` and a
user `<name>` both produce the key `user__<name>`, and any root-level item
with neither field keys to `user__undefined`, triggering Svelte's
`each_key_duplicate` and crashing the home screen.
Use a discriminated key (`f__`/`u__`/`i__` prefixes) that cannot collide
across folders, users, and items — the same scheme already used in Dev.svelte.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): remove unused slide import breaking frontend-check
`slide` (added in #9539) is imported but never used in AssistantMessage,
which fails `npm run check` (svelte-check --threshold warning) and reddens
the frontend-check baseline on main for every frontend PR. Remove it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: actually isolate windows job children from CTRL_BREAK_EVENT + reap on worker death
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* decouple LIMIT_WINDOWS_TO_1CU memory cap from DISABLE_PROCESS_GROUP escape hatch
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* create windows job children suspended, assign job, then resume (close post-spawn race)
Addresses Codex P1: AssignProcessToJobObject post-spawn could miss a grandchild forked before assignment. Creating the child suspended and resuming after assignment guarantees it is in the job before running any code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(otel): append system CA bundle to tracing proxy cert file
* chore: update ee-repo-ref to a82882f1cb9b1c4cef532f6ad046242903418d29
This commit updates the EE repository reference after PR #611 was merged in windmill-ee-private.
Previous ee-repo-ref: 2bc8ab492b6aa2cdfb81aff8236bd217b2681716
New ee-repo-ref: a82882f1cb9b1c4cef532f6ad046242903418d29
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat(frontend): improve AI chat cancel and interrupted-turn handling
- Escape stops the in-flight generation when focus is on the chat
(composer, messages, panel) — capture-phase listener so neither the
session Monaco editor nor mounted-but-closed modals swallow the key
- When a turn yields no output (or is cancelled before any), roll it
back and restore the message to the composer
- When a turn is cancelled or fails mid-way, keep the completed
tool-paired steps and the partial answer text as context so a
follow-up like "continue" picks up from there
- Animate the thinking-block collapse like tool boxes (slide 150ms)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): address review findings on interrupted-turn handling
- Guard the catch in sendRequest so a post-commit throw (e.g. saveChat)
cannot commit the turn a second time or mis-flag the user message
- Delete the persisted chat entry when rolling back a first turn empties
the transcript (saveChat no-ops on empty, leaving a stale entry)
- restoreInstructions skips when the user already typed a new draft
- Use stopImmediatePropagation so one Escape on body focus cannot cancel
several mounted chat panels at once
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(frontend): tighten comments and dedupe tests in AI chat changes
Keep each invariant comment once at the place it would be broken; drop
narration and repeated rationale. Remove near-duplicate test cases
(chatLoop boundary permutations, cancel-before-output subset).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: add comment policy to AGENTS.md core principles
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: comments must describe current code, not PR drafting history
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): treat completed reasoning-only turns as unsent
A turn that finishes without abort but emits only reasoning produced a
display bubble, so the empty-turn rollback (keyed on display output)
skipped it and the user message was silently swallowed. Key the
decision off usable output instead. Also trim comment blocks to the
AGENTS.md 4-line norm, splitting rationale to its break-site.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* feat: map chat reasoning effort to gemini thinkingConfig in ai proxy
* feat: support claude adaptive thinking through the bedrock ai proxy
* feat: per-model gemini effort levels and thought summary display
* fix: exclude claude opus 4.5 from reasoning effort (rejects adaptive)
* fix: render markdown in thinking blocks and unstick reasoning spinner
* feat: model-aware reasoning effort options across ai chat providers
* fix: scope openrouter reasoning off to the underlying model family
* feat(cli): add --yes, --secret/--no-secret and --description to variable add
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(cli): cover variable add create/update flag semantics
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cli): warn on secret downgrade in variable add and pin preserve semantics in test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: stop sending temperature for AI chat across all providers
Remove the temperature field from all AI chat completion requests and
drop the model-based send-or-not special-casing. Previously the chat sent
temperature: 0 for determinism and omitted it for reasoning models
(claude-opus-4-7/4-8, gpt-5+, o-series) that reject sampling params, which
required hand-maintaining a growing model list.
The model-detection helper is kept (renamed modelDisallowsSamplingParams ->
requiresMaxCompletionTokens) since it still serves a separate concern:
choosing max_completion_tokens over max_tokens for OpenAI/Azure reasoning
models on the Chat Completions API.
The FIM autocomplete temperature: 0 is intentionally left untouched (it is
the code-autocomplete path, not the chat).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: drop dead claude-* branches from requiresMaxCompletionTokens
After temperature removal, requiresMaxCompletionTokens only governs the
max_completion_tokens vs max_tokens choice for OpenAI/Azure reasoning
models (its sole call site is gated on provider === openai|azure_openai).
The retained claude-* branches were leftovers from when the function
omitted temperature for Anthropic reasoning models; they are unreachable
for the max_completion_tokens decision and made the kept detection tests
assert a semantically false claim. Claude reasoning behavior is owned by
reasoningRegistry. Drop the dead branches and their tests so name, comment,
code, and tests agree. Behaviorally inert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: refetch license key from settings when in-memory key is invalid
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: only record accepted license keys so rejected keys stay retryable
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: add get_app_runtime_logs tool to global chat
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: only handle raw app backend messages from the runner's own iframe
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add list_app_runs tool to global chat for raw app backend runs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: simplify raw app chat tool results
* nits
* nits
* chore: bump ui builder artifact
* fix: resolve pending runtime log requests on cleanup
* fix: harden raw app runtime log requests
* nits
* fix: show raw app tool results in status
* fix: cap raw app runtime log results
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): improve agent prompts/skills and workspace fork workflow
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): refuse fork --from-branch rename of a base branch
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(cli): auto-detect fork branch workflow, drop rt.d.ts refresh and legacy-name warning
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(skills): reconcile raw-app generate-metadata stance (agent offers+runs)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(skills): agent runs all CLI commands, gated on intent not on user typing them
Extends #9467's safe-vs-destructive model: the agent runs consequential commands (sync push, generate-metadata) itself too, gated on explicit user intent rather than handed to the user to type. The explicit-intent rule is the safeguard; an approval prompt is treated as a possible backstop, not assumed (auto-approve/headless runs have none).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Revert "docs(skills): agent runs all CLI commands, gated on intent not on user typing them"
Reverts 9225e1759b. That commit over-reached: #9467 already established the safe-vs-destructive split, and the targeted item-6 fix already removed the passive "tell the user they can run <safe next step>" phrasing. The blanket "agent runs everything" principle pushed deploys to be more eager and carried a wrong "permission layer prompts for approval" claim (untrue in auto-approve/headless mode). Keep deploys conservative.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): default fork workspace name/id to the current branch when renaming it
When 'wmill workspace fork' converts the current working branch into the fork branch, default the fork's name and id to that branch (sanitized to a slug, since branch names can contain '/'). Interactive: the prompt is pre-filled (enter to accept); non-interactive (--yes): used automatically. Adds a unit test for the slug derivation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): address fork review — guard fork-branch rename, cap+validate fork id
Two P2s from review:
- --from-branch refused when the current branch is already a fork branch (would detach the existing fork by renaming its branch).
- fork id slug capped to 42 chars (backend max 50 incl. wm-fork- prefix); auto-derived id is slugged; full id validated client-side before existsWorkspace/datatable cloning so an invalid id fails fast instead of leaving cloned Postgres databases behind.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): stop live activity flickering when user has multiple tabs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): use hasOwnProperty instead of Object.hasOwn for ts lib compat
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): show AI sessions in narrow-screen burger menu
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): show burger menu on sessions page in narrow setup
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: add reasoning effort control and thinking display to AI chat
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: strip legacy /thinking suffix from configured model slots
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: refine reasoning effort UX and drop dynamic-capability scaffolding
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: show textless reasoning on the typing indicator
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(worker): #ssh directive to run a bash script on a remote SSH host
Add a first-class `#ssh <resource_path>` bash directive that reroutes a
normal bash script to run on a remote host reached over SSH (a
jump/utility node) instead of on the worker, with full parity: typed
positional args in, structured result out, live streamed logs,
cancellation, and remote exit-code propagation.
It mirrors the existing `# sandbox <image>` precedent: the directive is
parsed in handle_bash_job and reroutes to a specialized handler that
reuses handle_child for all execution plumbing.
- windmill-common: BashAnnotations::ssh_target() parser (+ unit test)
and the ssh_execution_enabled instance setting (off by default)
- windmill-worker: reroute hook in bash_executor + ssh_executor_oss
shim. OSS returns a clear "enterprise feature" error; the real
handler lives in ssh_executor_ee.rs (private feature) and is gated by
a valid enterprise license + the instance setting.
- examples/usecase/ssh-execution-wrapper: the ssh_target resource type,
a userland wrapper (no-license fallback), and a README documenting
both paths and the trade-offs vs agent workers.
EE companion: windmill-labs/windmill-ee-private (ee-repo-ref.txt bumped).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(worker): ssh host-key opt-in, 0600 key write, instance setting UI
* chore: update ee-repo-ref
* feat(worker): #ssh $arg form to take the ssh target from a job argument
* fix(worker): #ssh token must look like a target; $arg restricted to path strings
* fix(worker): tighten #ssh parser to exact directive; add -- ssh destination guard
* chore: update ee-repo-ref to d45b9a6cbe40f7fe5d322c850c50f64a6980e4f0
This commit updates the EE repository reference after PR #609 was merged in windmill-ee-private.
Previous ee-repo-ref: 2804f1aa8e74b3a7733aeb6f5044d5085193872a
New ee-repo-ref: d45b9a6cbe40f7fe5d322c850c50f64a6980e4f0
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat(ai-chat): collapse big pastes, cap input height, escape HTML
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(ai-chat): concentrate paste expand/render in a ChatDraft module
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(ai-chat): step caret over paste chips as one unit
Arrow-Left/Right now jump edge-to-edge across a collapsed-paste chip
instead of crawling through the invisible token characters, and snap the
caret out if it lands inside a token. Shift extends the selection across
the whole chip; word/line jumps (alt/cmd/ctrl) are left to the browser.
Mirrors the existing atomic-deletion behavior so traversal and deletion
both treat the chip as a single object.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ai-chat): atomic chip deletion on overlapping selections + plural label
A selection that partially overlapped a paste token previously hit the
collapsed-caret early-return, so the browser deleted a partial token and
left an orphaned zero-width run plus a dangling pastes registry entry.
handlePasteDeletion now widens the deletion range to cover each overlapped
token whole and drops all affected pastes entries (shared removePasteRange
helper). Strict overlap, so abutting a chip edge doesn't pull it in.
Also route line-count pluralization through a shared lineCount() helper so
a 1-line paste reads "1 line" in the conversation bubble too, not "1 lines".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ai-chat): full chip atomicity via beforeinput + expand on copy/cut
Addresses review nits on PR #9487:
- Chip atomicity is no longer limited to Backspace/Delete keydown. A new
beforeinput handler takes over any selection-spanning edit that overlaps
a paste chip — typing over a selection, paste, drag-and-drop, and
Backspace/Delete over a selection — and applies it to the whole token(s)
via a shared replacePasteRange, so a partial edit can no longer leave an
orphaned zero-width run or a dangling pastes registry entry. handlePaste
is likewise widened so a large paste onto a chip replaces it whole. The
keydown handler now only covers the collapsed-caret-at-boundary case
beforeinput can't see.
- Copy/cut of a selection containing a chip now puts the expanded content
on the clipboard instead of the chip label + its zero-width run; cut also
removes the chip whole. This also removes the duplicate-token re-paste
vector (the clipboard never carries a raw token anymore).
- Rename lineCount -> lineCountLabel: it returns a formatted string, not a
count, so the name shouldn't read like an accessor.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ai-chat): address cubic + claude review findings on the paste feature
cubic-dev-ai findings:
- autosize: clear inline overflow-y on the uncapped path so a capped->
uncapped toggle can't leave a stale `auto`/`hidden`.
- pasteTokens: new countLines() ignores a single trailing newline, so a
10-line paste with a trailing \n no longer counts as 11 (off-by-one in
shouldCollapsePaste and the chip's reported line count).
- ContextTextarea: the @-mention picker anchor now subtracts the textarea's
own scrollTop/Left and is recomputed on internal scroll, so it stays
pinned once the input is capped at 40vh and scrolls.
claude re-review findings:
- ContextTextarea: drag-and-drop of a selection containing a chip now puts
the expanded content on the drag payload via ondragstart (mirroring
copy/cut), so a dragged chip no longer orphans its token and loses the
pasted content (or leaks the label to an external target).
- ContextTextarea: handlePasteBeforeInput now guards on e.cancelable and a
HANDLED_INPUT_TYPES whitelist, so historyUndo/Redo (Ctrl+Z) and
non-cancelable insertCompositionText (IME) are left to the browser
instead of being reinterpreted as a delete / rewritten mid-composition.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: clear conflict error + force delete when reusing a fork workspace id
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: guard fork force-delete against double submit
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(ai-chat): quick access to AI prompt settings from chat
Add a cog next to the AI model selector in the chat that opens a dropdown
to edit the current chat mode's user (localStorage) and workspace (DB
ai_config) AI prompts, reusing the shared AIPromptsModal. Workspace prompt
editing is admin-only; non-admins get a read-only view with an info Alert.
The dropdown and modal footer include an admin-only deep-link to the full
AI settings page, opening in a new tab.
Workspace save re-applies the saved custom_prompts onto the store after the
ai_config round-trip, since effective_ai_config falls back to the instance
config (and would drop them) when the workspace has no providers of its own.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ai-chat): snapshot mode when opening prompt settings modal
Prompt edits were keyed off the live chat mode (a $derived of
aiChatManager.mode). If the chat mode changed while the modal was open,
save/reset/hasChanges and the modal's target mode would follow the new
mode and write to the wrong key. Capture the mode into an activeMode
snapshot at open time and use it throughout the modal lifecycle.
Identified by cubic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ai-chat): base-prefix AI settings links and reset hasChanges after save
- Prefix the AI-settings deep-links (dropdown item + modal footer) with
{base} so they resolve under a deployment base path instead of 404ing.
- After a successful save, sync customPrompts to the trimmed value so
hasChanges flips back to false (Save/Reset disable, Reset no longer
snaps the textarea).
Identified by Claude review.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ai-chat): gate workspace prompt editing on workspace having own providers
When a workspace has no AI providers of its own (it uses instance defaults),
the backend never makes workspace custom_prompts effective — get_copilot_info
returns the instance config verbatim. A workspace prompt saved in that state
would be dead config that silently disappears on reload, and the earlier
re-apply hack also transiently replaced instance prompts in copilotInfo.
Mirror the settings page (AISettings.svelte): detect the workspace's own
providers in the same getSettings fetch used to seed the prompt, and when there
are none, surface the workspace prompt read-only with an explanatory Alert
(new readOnlyReason prop on AIPromptsModal) instead of an editable field.
Since editing is now gated to workspaces with their own providers,
effective_ai_config equals the saved config, so the re-apply is reverted to a
plain setCopilotInfo(effective).
Identified by Claude review.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ai-chat): await async save before closing prompts modal
AIPromptsModal.handleSave called onSave() without awaiting, then closed the
modal immediately — so the workspace save round-trip was still in flight when
the modal dismissed, with no loading state and any error toast landing after
the modal was gone.
Make handleSave await onSave (now typed to allow a Promise), show a loading
state on Save while in flight, and keep the modal open if the save throws.
AIChatSettingsMenu.save() re-throws on failure so the modal stays open. The
synchronous user-prompt path and existing callers are unaffected.
Identified by Pi review.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(frontend): clarify trigger filters evaluate decoded JSON, not base64
The filter description in TriggerFilters didn't explain that filters are
evaluated on the original decoded JSON payload, while a base64-encoding
trigger (Kafka) still delivers the payload to the runnable as a base64
string. Users saw base64 in their scripts and assumed filters couldn't
reference JSON keys.
Adds a `payloadBase64Encoded` prop (set by the Kafka editor) that appends
a sentence clarifying the runnable receives base64 while filter keys
reference the original JSON structure. WebSocket triggers deliver the raw
message string, so the base64 caveat is intentionally not shown there.
Fixes WIN-2029
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): reword trigger filter help to "parsed as JSON", not "decoded"
Nothing is base64-decoded during filtering: WebSocket parses the text
frame directly, Kafka converts the message bytes to UTF-8 then parses.
The base64 form only exists on the delivery path to the runnable (Kafka,
V2). Reword to "filters match against the message parsed as JSON" and, for
base64 triggers, clarify filters run on the message before encoding.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): correct filter-key example to match backend semantics
Review follow-up: the "data.status" example implied dot-path filter keys,
but the backend (SupersetVisitor in filter.rs) matches keys literally
against top-level JSON fields — "data.status" would never match a nested
object. Reword to state keys match top-level fields and nested matching
is done via an object value (key data, value {"status": "active"}).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: investigate pod-deletion-cost for k8s autoscaling scale-in (WIN-2028)
* docs: correct worker_instance aggregation claim per review
* docs: drop investigation doc in favor of implementation
* chore: bump ee-repo-ref for pod-deletion-cost autoscaling scale-in
* chore: update ee-repo-ref to 80c39bf7f3bfeda4cf0974ce32826f53affd9574
This commit updates the EE repository reference after PR #610 was merged in windmill-ee-private.
Previous ee-repo-ref: 51f79d4a49dd6491f4809f3edcb3919571719da7
New ee-repo-ref: 80c39bf7f3bfeda4cf0974ce32826f53affd9574
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* docs: replace dead 0x0.st with gh-based PR screenshot recipe
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: harden PR screenshot recipe (filename, secrets, CI fallback)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: make default chat model optional in AI settings
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: stop auto-seeding default chat model on provider enable
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(flow): support worker tag override on AI agent steps
* refactor: drop ineffective tag passthrough in nested agent tool path
* chore: regenerate openflow-derived system prompt artifacts
The modal's window keydown-capture handler called preventDefault() and
stopPropagation() on every keystroke while open, which swallowed Cmd/Ctrl+C
and Cmd/Ctrl+V (and blocked typing in any child input). Only intercept
Enter/Escape without modifiers and let all other keys through.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Adding or editing environment variables in the worker group config drawer
did not enable the "Apply changes" button. `hasChanges` compares the original
`config` prop against the local `nconfig` copy, but env var edits only mutate
the local `customEnvVars` array — they aren't synced into
`nconfig.env_vars_static`/`env_vars_allowlist` until the Apply handler runs.
This left the button stuck disabled despite real changes.
Add a `hasEnvVarChanges` derived that reconstructs the original env vars from
`config` and compares them to the current `customEnvVars`, and include it in
the Apply button's disabled condition.
Fixes WIN-2023
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai): add list_runs and get_job_logs tools to global chat mode
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai): always suppress ansi hint in get_job_logs, drop misnamed param
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(ai_evals): add global list_runs and get_job_logs eval cases
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(ai): trim get_job_logs description and format global core
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): surface list_runs/get_job_logs output as tool result
The tools set showDetails but never set message.result, so the details panel rendered "No result yet" even on success. Set result in setToolStatus (logs go in result for get_job_logs).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When HTTP request tracing is enabled, the NO_PROXY injected into traced jobs
was built solely from the no_proxy_hosts instance setting, ignoring the
worker container's own NO_PROXY. Enabling tracing therefore silently dropped
every exclusion an operator had already configured at the container level,
funneling those hosts into the MITM proxy (and on to any upstream corporate
proxy). The upstream-relay side already honored the container NO_PROXY; this
makes the injected-into-jobs side symmetric by merging both sources.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(skills): decouple safe local commands from destructive sync push
The schedules, triggers, and resources skill templates lumped every CLI
command under a blunt "do NOT run them yourself" directive. This conflated
two very different risk profiles and forbade the agent from running even
read-only/local commands, creating needless friction.
Align these three with the nuanced policy flow-cli.md already uses: keep
`wmill sync push` defensive (it deploys and can be destructive to remote
state — only run when the user explicitly asks to deploy/publish/push),
while letting read-only commands (`sync pull`, `schedule`, `resource
list`) be run freely. Regenerated auto-generated skills + skills.gen.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): warn that sync push is destructive in dry-run output
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(skills): clarify sync pull mutates local files, not read-only
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: centdix <farhadg110@gmail.com>
* fix(cli): reconcile case-only path drift during sync on case-insensitive filesystems
Windmill paths are case-sensitive, but Windows (and the default macOS
setup) use case-insensitive filesystems. The real-world failure behind
WIN-2020 is not a user authoring both f/Caps and f/caps — it is a single
capitalized folder whose on-disk casing silently drifts (Windows stores
and reports whatever case the directory was first created with,
regardless of the server's path). The diff then sees the drifted local
path as a brand-new item and emits a destructive "delete f/Caps +
add f/caps" pair, so a capitalized folder appears to vanish and a
lowercase clone shows up out of nowhere — and a push can clobber the
real server item.
Fix: on a case-insensitive filesystem, reconcile case-only drift before
diffing. The server's path casing is authoritative, so compareDynFSElement
now rewrites local keys that differ from a remote key only by case to the
server's casing (canonicalizeCaseInsensitiveKeys), making the diff treat
them as the same item. Case-insensitivity is auto-detected by probing the
sync directory, with a WMILL_CASE_INSENSITIVE_FS=true/false override to
force Windows behaviour (or emulate it for tests / cross-platform repos)
on any host. Reconciled paths are summarized in a single info line.
Genuinely unrepresentable collisions — two DISTINCT server paths that
differ only by case — cannot be canonicalized to one target; those are
detected and warned about on every platform so a case-sensitive-Linux
author learns their tree won't round-trip for a Windows/macOS teammate.
Tests:
- Pure unit tests for findCaseInsensitiveCollisions,
canonicalizeCaseInsensitiveKeys and summarizeCaseRewrites (platform
independent).
- An end-to-end drift test that runs on BOTH CI jobs: on the Windows
runner it exercises the real case-insensitive NTFS + auto-probe; on
Linux it reproduces the drift via rename, asserts the destructive
phantom appears without the fix, and asserts a clean no-op push with
the fix forced on.
Fixes WIN-2020
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): canonicalize local-only descendants of drifted folders; dedupe nested case collisions
Address two review findings on the WIN-2020 case-insensitive sync fix:
P1 (correctness): canonicalizeCaseInsensitiveKeys previously only rewrote
local keys with an exact full-path remote match. A brand-new local file
under a drifted folder (e.g. adding f/caps/New.ts when the server has
f/Caps but no f/caps/New.ts) had no exact match, so it kept its lowercase
casing and push uploaded it as-is — recreating f/caps beside f/Caps and
reintroducing the very collision the fix prevents. Canonicalization is now
segment-by-segment against a trie of remote paths, so local-only
descendants inherit the longest unambiguous server folder casing. A segment
is only adopted when the server casing is unambiguous; at the first
ambiguous/unknown segment the remainder keeps local casing. The original
key's separator style is preserved so rewritten keys still round-trip.
P2 (nit): findCaseInsensitiveCollisions reported the folder group AND a
nested per-file group when case-variant folders held same-named files,
inflating the "Found N path(s)" count. It now reports only the shallowest
clash (drops a group whose ancestor prefix is itself a collision).
Tests: add unit coverage for the new-file-under-drifted-folder rewrite, the
stop-at-first-unguided-segment behavior, and shallowest-only collision
reporting; extend the e2e drift test to assert a new item added under the
drifted folder is pushed under the server's folder casing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: deployed↔draft compare for current workspace + session draft bar
Add a "Deployed ↔ draft" comparison alongside the existing fork-vs-parent
compare flow, and surface drafts in the AI session UI.
- Merge the fork-direction toggle (Deploy to parent / Update current) and
the new deployed↔draft mode into one 3-way CompareModeToggle, rendered
inside the comparison card. Hidden in non-fork workspaces (draft only).
- CompareDrafts: list/deploy/discard server drafts (scripts, flows, apps
incl. raw apps) via shared WorkspaceDeployLayout.
- Session draft bar (SessionDraftBar) mirrors the fork bar, only visible
when drafts exist; its diff button opens the shared read-only diff
drawer extracted as WorkspaceDiffDrawer (ForkDiffDrawer + DraftDiffDrawer
are thin wrappers over it).
- WorkspaceDraftsBanner: home banner linking to draft review.
- Backend: GET /drafts/count endpoint for the draft-count badge.
- Raw app draft deploy (rawAppDeploy.ts) + vite /ui_builder proxy headers
so the bundler iframe loads cross-origin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: refine draft/fork compare toggle UX
Follow-up polish on the merged deploy/draft compare control:
- Relabel the draft toggle to "Deploy draft (N)" and show per-direction
counts on all three toggle buttons (deployable / updateable / drafts),
suppressed when zero. Counts are computed page-side so they persist in
draft mode too.
- Warn before deploying to the parent when the fork has undeployed drafts
("Only deployed versions in this fork can be sent to {parent} …") with a
one-click link to the draft view; milder note in the update direction.
- Show an empty-state message per direction ("Nothing to update — this
fork is up to date with {parent}") instead of a table of greyed,
non-actionable rows; hide the deploy/update button in that case.
- Drop the standalone "Pending drafts" info alert from the draft list.
- Align the fork "Show diff" button to the non-deprecated Button API
(unifiedSize, onClick, startIcon) so it matches the draft one; mark
"Discard draft" destructive.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: link compare row titles to the item editor
- Render each compare row title (fork and draft) as a link that opens the
item in a new tab, scoped to the current workspace (raw apps route to
/apps_raw/edit), matching the AI-session diff drawer: target=_blank, hover
underline + ExternalLink icon, click stops row-selection propagation.
Kinds without an editor stay plain; the fork rename markup is preserved.
- Drop the "Kind → name" prefix from draft rows — that arrow reads as the
rename visual and the kind is already shown by the row icon.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: clickable rows + multi-select in deploy layout
- Make deploy-layout row cards selectable on click via an opt-in
`selectOnRowClick` prop on the shared Row (default off, other tables
unaffected); clicks on the checkbox, title link and action buttons are
ignored. Adds role/tabindex + Enter/Space keyboard support.
- Support multi-select with modifier keys like classic list pickers:
Shift+click selects the contiguous range from the anchor row; Cmd/Ctrl
(and plain) click toggles a single row. select-none avoids text
highlighting on shift-click.
- Turn the "Select all" text into a <label> associated with its checkbox
so clicking the text toggles it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: select all drafts by default in draft compare
Drafts now load pre-selected (deploy-all is the common intent); guarded so
a reload after a deploy doesn't re-select the items left behind. Mirrors
CompareWorkspaces' default auto-selection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: show diff for draft-only items stored without a draft row
A draft_only flow/script/app whose content lives in the entity row itself
(created via create*(draft_only: true), no separate draft-table row — like
u/admin/new) returns draft == null from get*ByPathWithDraft. getDraftDiffValues
passed that null through, so the diff "after" side was empty and nothing
rendered. Fall back to the row's own value as the draft content when draft is
null (deployDraft already did this), fixing both the compare-page DiffDrawer
and the session bars' WorkspaceDiffDrawer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: shared diff button across session bars + bar spacing
- Extract SessionDiffButton (variant=default, ± DiffIcon, count, "Open diff"
title) and use it for the diff-drawer trigger in both the fork bar and the
draft bar, so they're identical. Drop the icons from both "Review" buttons.
- Add gap-1 (4px) between the fork bar and draft bar when both are visible
(flex wrapper; single in-flow root per bar, drawer is portalled — no stray
gap when only one shows).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: deploying a new (draft-only) flow or app
A draft_only flow/app already has an entity row (created via
create*(draft_only: true)), so deployDraft's createFlow/createApp rejected it
with 400 "already exists". Use updateFlow/updateApp instead — a listed draft
always has a row, and update promotes a draft_only entity to a real deployed
version (clearing the flag), like the editor does. Scripts were unaffected
(createScript + parent_hash makes a new version).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: refresh fork comparison after deploying/discarding a draft
Deploying a draft promotes it to the workspace's deployed version, changing
the fork comparison (ahead/behind vs parent) — but the compare page only
re-fetched it on workspace change, so the deploy/update toggle counts and the
CompareWorkspaces tab went stale. CompareDrafts now fires onChanged after a
successful deploy/discard; the page rewires it to refresh the comparison and
draft count.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: stop draft-count effect from freezing the AI session page
ensureDraftCount cleared its dedupe key on error; since the caller is a reactive $effect (SessionDraftBar), a persistently-failing countDrafts spun the effect into an infinite retry loop that flooded the console and froze the tab. Claim the key before awaiting and keep it set on failure; refresh*() still forces a re-fetch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: correct draft count and refresh compare counts after actions
count_drafts now counts deployable drafts (draft_only OR has-a-draft-row across script/flow/app), matching the CompareDrafts list, instead of raw draft-table rows which miss new draft-only items. CompareWorkspaces and CompareDrafts fire onChanged so the compare page re-fetches the comparison and draft count after deploy/update/discard, keeping the toggle badges in sync.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: session draft bar shows a fresh count on every (re)open
The runtime persists across client-side navigation, so the deduped
ensureDraftCount() kept a stale count (e.g. a 0 cached before a draft was
created) when a session was re-opened — the bar stayed hidden even though
the server count was >0.
Force one fresh fetch per mount from a non-reactive onMount via
refreshDraftCount(workspace) (which now takes the workspace so it works
before the dedupe key is set). The reactive $effect keeps using
ensureDraftCount: refreshDraftCount reads loadingDraftCount ($state), so
calling it from an effect would track-and-mutate that state into an
infinite fetch loop — ensureDraftCount's plain-key early-return avoids it.
ensureDraftCount also now releases its key after a 5s backoff on failure so
a transient countDrafts error retries instead of leaving the bar stuck.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: make the draft count a single deep Workspace Drafts module
The Draft Count was computed four ways (backend count_drafts SQL, the
CompareDrafts list filter, a bespoke sessionRuntime cache, and the compare
page state) that drifted — the root cause of the unreliable count, the
stale-on-reopen bug, and the effect-loop freeze.
Introduce one module (workspaceDrafts.svelte.ts):
- getDraftItems(ws) lists the deployable Draft Items once; count ≡ list length,
never a separate query.
- useWorkspaceDrafts(() => ws) is a component-scoped runed resource (fetches on
mount + ws change, no persistent cache → fresh on every (re)open).
- invalidateWorkspaceDrafts(ws) refreshes mounted consumers; deployDraft/
discardDraft self-invalidate, so callers never reason about staleness.
Rewire every reader to it (SessionDraftBar, CompareDrafts, DraftDiffDrawer,
WorkspaceDraftsBanner, compare page) and delete the sessionRuntime draftCount
apparatus (key + loading flag + 4 methods + effects + backoff). With no caller
left, remove the count_drafts endpoint (handler, route, openapi, sqlx cache,
generated client) — drafts.rs/openapi return to their main state. Record the
draft vocabulary in CONTEXT.md.
A single GET /w/{ws}/drafts/items endpoint can later replace getDraftItems'
three list calls behind the unchanged seam.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: warn before deploying a draft based on an outdated version
When a newer version is deployed while a draft exists (git-sync/CLI deploys preserve drafts via skip_draft_deletion), the compare/deploy-drafts page now flags the draft as Outdated and gates deploy behind an override confirmation with a diff — instead of silently clobbering the newer version. Staleness is read from the draft's base version: scripts already store parent_hash; flows/apps now record a draft_base_version sidecar on save.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: drop redundant /ui_builder proxyRes hack (superseded by #9433)
main's global configure-response-headers plugin now runs with enforce:'pre'
and sets COOP/COEP/CORP on dev responses (#9433), so the per-proxy proxyRes
override is no longer needed. Revert the /ui_builder block to main's headers
form — vite.config.js now matches main with no branch-specific change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(sessions): keep draft count reactive to preview/chat deploys
Invalidate the Workspace Drafts resource at every frontend deploy seam
(ScriptEditorView / FlowEditorView / RawAppEditorView onDeploy + onSaveDraft)
so user-driven deploys from the Preview panel update the count immediately,
and refresh SessionDraftBar on the same coarse signals SessionForkBar uses
(AI turn-end + tab refocus) to cover chat-driven deploys that happen
server-side and never surface as frontend calls.
Also: always show the draft toggle count including (0) on the compare page,
drop the header/content separator line in both compare cards, and derive the
"Deploy N drafts" footer count so it stays reactive after discard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: address branch review findings
- WorkspaceDraftsBanner: use the modern Button API (variant/unifiedSize/onclick)
instead of the deprecated size/color/on:click triad; drop "pending" from the
banner copy to match CONTEXT.md vocabulary.
- WorkspaceDeployLayout: make Cmd/Ctrl-click distinct from a plain click.
Plain row click now selects only that row (classic file-picker), Cmd/Ctrl
toggles, Shift extends the range, and the checkbox still plain-toggles.
Adds an onSelectOnly callback wired in CompareDrafts/CompareWorkspaces.
- WorkspaceDiffDrawer: document why the file filter is a raw input (bespoke
keyboard-nav integration the design-system inputs can't express).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* revert: drop draft version-gating (stale-draft warning)
Remove the "deploying an outdated draft would override a newer version"
guard. It's a rare edge case and will be handled properly by conflict
resolution in a follow-up PR.
- CompareDrafts: drop staleMap/computeStaleness, the TOCTOU pre-deploy
re-check, the "Outdated" badge, the override-in-diff button, and the
"Newer version deployed" confirmation modal; deploySelected is now the
plain deploy.
- utils_draft_deploy: remove getDraftStaleness/DraftStaleness and the
draft_base_version strip.
- FlowBuilder / AppEditorHeader / RawAppEditorHeader / AppJsonEditor: stop
injecting draft_base_version into draft saves — these editor paths are
back to matching main, shrinking the PR's blast radius.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style: unify home banner CTAs on the modern Button API
Both the Workspace Drafts banner and the sibling Fork banner now use
variant="default" unifiedSize="sm" onclick, so the two CTAs on the home
page render identically and neither uses the deprecated size/color/on:click
props.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(compare): show draft deploy direction badge inside forks
Mirror the fork compare header's "from → into" badges on the Deploy-draft
tab: "deploy: draft → into: <fork>". Makes it explicit that deploying a
draft promotes it within the fork (deployed↔draft), not up to the parent.
Only rendered inside forks, where the parent could otherwise be confused
with the deploy target.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(compare): address PR review — dedup drafts resource + shared link
- De-dupe the compare page Workspace Drafts fetch: the page owns the single
resource and passes draftItems/draftsLoading into CompareDrafts (was mounting
a second resource → 6 list calls; now 3).
- Prune transient deploymentStatus for items dropped from the list (no unbounded
growth, no stale 'deployed' suppressing a re-drafted row).
- Type getDraftItems' list fields via a narrow DraftListEntry (drop Array<any>).
- Clear comparison catch-up timers on unmount (onDestroy).
- Extract shared ExternalEditLink.svelte; use it in CompareDrafts,
CompareWorkspaces, WorkspaceDiffDrawer (was a near-verbatim <a> block x3).
- Note conflicts intentionally count in both toggle directions; drop a stray
blank line in sessionRuntime.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(compare): show draft summary renames via shared item-summary component
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(compare): address round-2 PR review
- Point the fork-compare edit link at the workspace the item actually lives
in: a parent-only row (absent in the fork) would 404 if linked into the
fork, so link it into the parent instead.
- Replace the bespoke raw <button class="underline">Deploy drafts</button> in
the undeployed-drafts alert with a design-system Button (variant=subtle).
- Drop the stray Prettier reflow in sessionRuntime (restore to match main).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(compare): warn on fork items with a pending draft
In the fork compare list, items that are deployed *and* have a pending
draft (has_draft) now:
- show a yellow "+Draft" badge (AlertTriangle), rendered before the
New/status badges, with a per-direction tooltip explaining that
deploying/updating moves the deployed version, not the draft;
- are excluded from the default selection (still manually selectable);
- trigger a confirmation modal if explicitly selected and deployed/updated,
listing the affected paths.
The signal comes from the page's existing fork drafts resource (a
kind:path Set passed down) — no new fetch, no backend change. Also rename
the undeployed-drafts alert CTA from "Deploy drafts" to "See drafts".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(compare): rename page to "Compare & Deploy"
Update both the page heading (PageHeader) and the browser-tab title.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(compare): multi-select rows by default (no modifier)
In the shared WorkspaceDeployLayout (fork + draft lists), a plain row
click now toggles the item in/out of the selection instead of replacing
the whole selection with it. Removed the modifier-based selection
entirely: the now-dead onSelectOnly path and its two call sites, plus
shift+click range selection (and its anchor/isPickable helpers).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(table): don't toggle row selection on keyboard child activation
Row's onkeydown selection handler lacked the interactive-child guard that
handleRowClick already had, so pressing Enter/Space on a checkbox, action
button, or title link both activated the child and toggled the row's
selection. Extract a shared fromInteractiveChild() guard and apply it in
handleRowKeydown, mirroring the click path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(fork-banner): show draft CTA when fork is up to date
When a fork has no changes vs its parent ("Everything is up to date") but
has pending drafts, the banner now mirrors the non-fork drafts banner:
the status text becomes "This workspace has N draft(s)" and the button
becomes "Review & deploy drafts", linking to the compare page in draft
mode. When the fork has real ahead/behind diffs, the existing status and
buttons are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(compare): honor renamed draft paths + raw-app draft fixes
Address the Codex review:
- Draft deploy now uses the draft payload's path for scripts, flows and raw
apps (keeping the URL path as the existing item key), so a rename in a
draft deploys to the new path instead of silently staying at the old one.
- DraftDiffDrawer maps raw apps to the `raw_app` kind so their row edit
links open the raw-app editor, not the legacy app editor.
- ScriptEditorView.restoreDeployed invalidates the workspace drafts after
deleting the draft, so the session draft-bar count drops immediately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(compare): guard showDiff race, tree label, mode fallback
Address the cubic review:
- CompareDrafts.showDiff uses a monotonic request token so two quick "Show
diff" clicks can't let a slow earlier fetch overwrite a faster later one.
- WorkspaceDiffDrawer.buildTree labels a 2-segment path with its leaf name
(parts[1]) instead of the full scope key.
- The compare page only resolves ?mode=draft immediately; ?mode=fork (and
an absent mode) defer to the isFork-aware effect, which falls back to
draft for non-fork workspaces instead of stranding them on the fork UI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: remove CONTEXT.md from the PR
Drop the root CONTEXT.md domain glossary and the lone comment pointer to
it in workspaceDrafts.svelte.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(compare): send custom_path on raw-app draft deploy
A raw-app draft that changes or clears its custom route was silently
dropped on deploy from the compare page: updateAppRaw omitted custom_path,
so the backend preserved the old route. Send the draft's custom_path on
update — matching the fork deploy path (which spreads the full app,
custom_path included) and the createAppRaw branch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(sessions): refresh draft count on raw-app session save-draft
The script/flow session editors invalidate the workspace drafts on
save-draft, but the raw-app editor only did so on deploy. Thread an
onSaveDraft callback through RawAppEditor → RawAppEditorHeader and call
invalidateWorkspaceDrafts from RawAppEditorView, so saving a raw-app draft
in an AI session updates the SessionDraftBar count immediately (and the bar
appears when the count was zero).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(compare): honor renamed paths, draft triggers & paginate inventory
Address the Codex review:
- Draft deploy honors the draft's renamed path for scripts/flows/raw apps
(keeping the URL path as the existing item key).
- Script/flow draft deploy now deploys draft_triggers via the shared
deployTriggers, instead of silently dropping them with the draft.
- rawAppDeploy sends custom_path admin-gated on update (admin: value/'' to
clear; non-admin: undefined) so non-admins don't hit RequireAdmin.
- getDraftItems pages through listScripts/listFlows/listApps so drafts past
the first page are included in the count, banners, drawer and deploy list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(compare): admin-gate custom_path on visual-app draft deploy
The visual-app branch of deployDraft sent custom_path unconditionally on
updateApp, so a non-admin deploying an app draft for an app with a custom
route hit RequireAdmin. Mirror AppEditorHeader and the raw-app path: admins
send the draft's custom_path ('' clears), non-admins send undefined so the
backend preserves the existing route.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(raw-app): save initial draft directly when path is known
In the AI-session preview, a never-deployed raw app has newApp=true but a
known path, so saveDraft opened the "Initial draft save" path-picker drawer
— which is gated on `appPath == ''` and therefore never rendered, making
Save draft silently do nothing. Branch the new-app case on appPath: pick a
path via the drawer only when none is chosen yet; otherwise call
saveInitialDraft() directly. saveInitialDraft now also toasts and fires
onSaveDraft so the session draft-bar count refreshes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(compare): preserve deployed custom_path on visual-app draft deploy
The visual-app draft value usually omits custom_path, so the admin branch's
`d.custom_path ?? ''` sent an empty string, which the backend treats as
"clear the route" — an admin deploying a content-only draft would wipe the
app's existing custom route. Fall back to the deployed route
(`r.custom_path`) when the draft omits it; an explicit '' still clears.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(api): add endpoint to update token label
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(api): prevent renaming the session token label
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(api): restrict token-label edits to user tokens, not just session
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): edit token label in the edit modal instead of inline
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(api): reject relabeling tokens to reserved system-token names
Centralize the is_user_token classifier in windmill-common and reuse it
to reject labels colliding with system-token namespaces (ephemeral*,
debugger-token, mcp-oauth-*), not just session.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(api): match ephemeral label case-insensitively and cap label length
Align the canonical is_user_token, the SQL guard and the frontend mirror on
a case-insensitive `ephemeral` match (so a token can't be relabeled to a
casing the backend allows but the UI hides), reject labels over the
VARCHAR(1000) column limit with a 400, and add unit tests for is_user_token.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: refresh session editor preview on breadcrumb target switch
Consolidate the three session editor views into a SessionEditorTarget deep module that remounts the heavy editor on a data-ready target swap ({#key slot.loadedPath}), so stale mount-time state (e.g. Path.svelte's settings-panel path) re-derives. Adds LoadSlot to the runtime and a useUserDraftSync composable + per-kind codecs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: flush pending session draft write on target switch
A breadcrumb target swap (or unmount) within the 150ms outbound debounce window cleared the pending UserDraft write instead of flushing it, dropping the last edits. Scripts previously saved immediately so this was a regression from the new uniform debounce; flow/raw_app already had the latent drop. A dedicated path/workspace-scoped effect now flushes the pending write on switch/unmount without disturbing the debounce during a typing burst. Also refreshes a stale loadScript comment that named removed symbols (addresses PR review nits).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(forks): defer trigger/schedule state to parent for clean git merge
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(forks): read parent trigger/schedule state on non-RLS pool for complete substitution
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(forks): read schedule fork-ness on non-RLS pool; clarify mutator-rule wording
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The lazy on-fetch OAuth token refresh persisted the new access token with a raw
`UPDATE variable SET value = <db-encrypted>`, bypassing the secret-backend
abstraction. With an external secret backend (AWS Secrets Manager / Azure Key
Vault / Vault), secret reads resolve through the backend and ignore
`variable.value` entirely, so refresh advanced `account.expires_at` and updated
Postgres but never wrote the new token to the external store. Every read that
did not itself trigger a mint kept serving the frozen connect-time token, which
expired ~1h after connect (RefreshError on Google clients).
`windmill-oauth` can't depend on `windmill-store` (circular), so variable
persistence moves out of `refresh_token{,_for_account}` (which now only exchange
the token + update the `account` row and return the new token) into the
`windmill-store` callers, via a new `store_oauth_token_value` helper that writes
through the configured backend and stores the returned value (encrypted blob for
the DB backend, `$...:` marker for external backends) in `variable.value`.
If persisting the refreshed token fails (more likely now that it can be a
network write to an external backend) after the account was committed fresh,
`store_oauth_token_value` resets `expires_at` to the past and records
`refresh_error` — looking the account up via `variable.account` — so the next
fetch retries instead of serving the stale token for the whole token lifetime.
Also add `windmill-store/tests/oauth_refresh_secret_backend.rs`, an opt-in e2e
regression suite (RUN_SECRET_BACKEND_E2E / RUN_AWS_SM_TESTS) covering database
and external (AWS SM via LocalStack) backends plus the self-healing reset.
Verified against Postgres + LocalStack: 3 passed.
EE companion (oauth_refresh_ee.rs: 3 refresh paths) merged via #607; this OSS
half completes the fix (ee-repo-ref already at EE main 481ea7f).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a configurable `defaultTimeout` to the script/flow editor whitelabel
customUi (replaces the hardcoded 300s default) and an `onTestJob` callback
on ScriptBuilder/FlowBuilder that fires with the preview job id when a test
run starts.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): use unified drill picker for AI chat @-mention dropdown
* fix(frontend): chat picker review followups + overlay alignment
- AIChatDisplay: migrate @-badge popover to ChatContextPicker (was still
importing the deleted AvailableContextList after the rebase onto #9034,
causing a build break).
- DrillPicker: handle Tab as Enter so the inline @<word> mention completes
without losing focus. Tweak leaf-row weight to font-normal; secondary
text uses text-hint.
- ContextTextarea: drop px-0.5 from the highlight span — extra horizontal
padding made every glyph typed after a mention drift right of the
invisible textarea below. box-decoration-clone keeps the rounded corners.
- ContextElementBadge: explicit font-normal label, hoist label into a
{@const} and pass to title= so the truncated badge shows the full title
on hover.
- workspaceTree: drop orphaned doc-comment left dangling by the rebase.
- Add unit tests for drillPicker.ts and workspaceTree.ts (51 tests cover
resolveScope/scopeChain/collectLeavesGrouped/leafHaystack, buildWorkspaceTree
shape + loading + dir forest + leaf shape, withCurrent rename suppression,
extraItemsByKind dedup, legacyScopeToPath, relativizeWorkspacePath).
* fix(flow-editor): ignore keyboard shortcuts when focus is outside the flow root
Menus, modals, drawers etc. live outside the flow root and capture focus
explicitly. Flow nodes aren't focusable, so the unfocused default
(activeElement === body) means "flow is the canvas" and we should react;
anything else means another surface has the user's attention and our
shortcuts would steal it.
* fix(frontend): inline @ mention picker + chat layout polish
- ContextTextarea: swap manual Portal+caret-math positioning for
svelte-floating-ui anchored at the `@` character (virtual reference,
middleware [offset, flip(crossAxis:false), shift]). Picker stays
pinned to `@` while the user types the query, slides leftward when
hitting the right edge instead of flipping alignment, and floating-ui
handles above-vs-below + edge clamping automatically. Drops the
60vh-worst-case reservation that left a big gap above the caret in
sessions, and the now-unused isFirstMessage prop is marked deprecated.
- AIChatDisplay: the `@`-button Popover now opens with placement
bottom-start (was the default `bottom`), aligning its left edge with
the button instead of centering under it.
- ChatContextPicker: when no Diffs/Modules/Databases branches are
present (e.g. global chat), return the Workspace tree's children at
the root instead of wrapping them under a redundant "Workspace" row.
handleScopeChange handles both the wrapped and unwrapped layouts and
the single-kind `dir:` top segment.
* chore(frontend): address review suggestions on chat picker PR
- DrillPicker: clamp width to viewport on narrow screens —
w-[420px] → w-[min(420px,calc(100vw-20px))].
- workspaceTree.buildWorkspaceTree: make loadingKind optional (defaults
to {}). Chat picker still passes it; callers that don't track loading
no longer need to thread an empty object.
- ChatContextPicker.handleScopeChange: name the WRAPPED vs UNWRAPPED
layouts in a comment block so the dir:/kind: branches are obvious.
- ContextTextarea: drop deprecated isFirstMessage prop (floating-ui
handles direction); drop defensive Math.max on the @ index now that
the invariant is documented; comment the floatingRef(anchorRef) call
as the supported virtual-reference path in svelte-floating-ui.
- AIChatInput: stop forwarding isFirstMessage to ContextTextarea.
* feat(frontend): sync selectedContext with @-mentions in textarea
Both picker entry points now insert a visible `@title` token in the
textarea, and deleting that token drops the matching entry from
selectedContext.
- AIChatInput: new insertMention(title) export. Appends `@title ` to
instructions, prefixing a space only if the existing text doesn't
already end in whitespace.
- AIChatDisplay: the `@`-button popover calls insertMention after
addContextToSelection so its picks match the inline-mention path's
textarea state.
- ContextTextarea: new onRemoveContext callback. A $effect compares the
set of `@title` tokens in `value` (derived) against the previous
snapshot; titles that disappeared trigger onRemoveContext for any
selectedContext entry with `deletable !== false`. The diff lives in
an effect (not handleInput) so it catches both keystroke deletions
AND programmatic value updates from updateInstructionsWithContext.
- AIChatInput: passes onRemoveContext that filters selectedContext by
type+title — mirrors the existing badge X-button handler.
* chore(frontend): narrow ChatContextPicker `inner` from `any` to `DrillPicker | undefined`
The previous `let inner: any` worked around svelte-check rejecting
`DrillPicker<ChatLeafData>` (the imported component is seen as the
non-generic `Comp`). Dropping the type parameter keeps the workaround
without `any`, so handleKeydown / pickHighlighted are at least typed
at the call site.
Addresses May-14 PR review.
* fix(frontend): address PR #9159 bot-review findings (eager preload, focus, dedup, icon types)
- [P1] ChatContextPicker.handleScopeChange: stop preloading workspace
kinds at the wrapped picker root. New `isWorkspaceOnly` $derived (true
when no Diffs/Modules/Databases branches are present) gates the at-
root preload, so the chat root no longer fires two list requests
before the user enters Workspace. Reported by Codex.
- [P2] AIChatDisplay @-button popover: call aiChatInput.focusInput()
after close() so the textarea is focused for immediate typing — mirrors
the inline-mention path's setTimeout(textarea.focus, 0). Reported by
Claude.
- [P2] AIChatInput.insertMention: no-op when the `@title` token is
already present in instructions, so re-picking a workspace item
doesn't leave duplicate visible tokens for a single selectedContext
entry. Reported by Codex.
- [P2] drillPicker.ts: introduce `DrillIcon = ComponentType |
Component<any, {}, ''>` and replace `icon: any` on DrillLeaf,
DrillBranch, and ChatContextPicker.buildContextBranch. Mirrors the
ComponentType | Component pattern used in TriggersBadge.svelte for
the same Svelte 4/5 compatibility window. Reported by Pi.
* fix(frontend): preserve workspace context on refresh + load all kinds for internal search
- [P1, Codex] ContextManager.updateAvailableContextForScript/Flow:
preserve workspace_script and workspace_flow entries through the
selectedContext filter on editor refresh. They're user-picked refs
that don't appear in availableContext, so the previous filter was
silently dropping them whenever the script/flow editor refreshed
options (e.g. on any code change).
- [P2, cubic-dev-ai] WorkspaceItemDrillPicker: in internal-search mode
(externalFilter === undefined, DrillPicker renders its own search
box), preload all kinds on mount. Without this, typing in the
picker's search before clicking a kind branch produced incomplete
results since DrillPicker can't reach back through the adapter to
trigger fetches on internalFilter change. Cached items keep the
effective cost near-zero on warm sessions.
* fix(frontend): preserve workspace refs through script-mode context refresh
The script-mode updateAvailableContext overwrites newSelectedContext
with a fresh [code] entry, defeating the workspace_script /
workspace_flow preservation in the later filter — the entries are
already gone by the time the filter runs.
Seed newSelectedContext with the refreshed code block AND the user-
picked workspace_script / workspace_flow / code_piece entries from
currentlySelectedContext, so editor refreshes don't wipe @-mention
badges in script chat. The existing line-271 filter still validates
each entry against newAvailableContext + the per-type allowlist.
Reported by Codex on PR #9159 — completes the prior workspace-context-
on-refresh fix (b02d1f2d35) which only patched the filter, not the
rebuild step that runs before it.
* fix(frontend): preserve all previously-selected contexts on script refresh
The prior c2775fe0c5 fix only carried over workspace_script /
workspace_flow / code_piece entries from currentlySelectedContext.
That preserved the workspace P1 path but still dropped previously-
selected diff / error / db / runtime-context badges, which cubic
flagged in its 16:55 review.
Spread the full currentlySelectedContext (minus `code`, which we just
rebuilt). The downstream filter validates each entry against
newAvailableContext + the per-type allowlist, so auto-derived types
like diff / error / db survive when still applicable, and unrelated
items are dropped automatically.
Reported by cubic-dev-ai on PR #9159.
* fix(frontend): rehydrate auto-derived context + sync badge X with textarea
- [P2, cubic] ContextManager.updateAvailableContext: when the rebuild
carries over previously-selected diff/error/db entries, swap each one
for the matching freshly-built entry from newAvailableContext in the
final .map() step. Preserves the user's `deletable` override on top
of the fresh content/diff/schema, so refreshes don't keep stale
payloads while still surviving the badge across edits.
- [P2, Pi/Codex] AIChatInput: new `removeMention(title)` export that
strips `@title` tokens from `instructions` (whitespace-bounded so
substring matches don't bleed). The badge X-button now calls it
after filtering selectedContext, mirroring the inverse textarea-to-
badge sync. No double-remove: ContextTextarea's $effect-driven
onRemoveContext is a no-op once selectedContext no longer holds the
entry.
* fix(frontend): retype ChatContextPicker.inner to DrillPicker<ChatLeafData>
`npm run check:fast` (TypeScript-only) and `npm run check` (svelte-check)
disagree on whether the imported DrillPicker is generic — `check:fast`
sees it as `Comp` and rejects the type parameter, while `svelte-check`
sees the real generic component and requires it. CI runs `check`, so
follow that: `DrillPicker<ChatLeafData> | undefined`.
This also fully replaces the prior `inner: any` workaround called out in
multiple bot reviews — handleKeydown / pickHighlighted now type-check at
the call site against the correct component instance.
* fix(frontend): scope removeMention's whitespace collapse to the mention site
The trailing `.replace(/ +/g, ' ')` in `removeMention` was global,
collapsing any pre-existing double-spaces in the prompt — e.g. a user
typing `"hello world @foo bar"` lost their intentional formatting
when they deleted the `@foo` badge.
Rework the regex to match `(^|\s)@title(\s|$)` and decide per-match:
- Mention at a boundary (no lead or no trail): drop entirely.
- Mention in the middle: keep ONE bordering whitespace char (the
leading one verbatim, so newlines/tabs aren't downgraded to spaces).
No global pass over `instructions`. Unrelated whitespace stays intact.
Reported by cubic-dev-ai on PR #9159 (07:27 review of 9e07eac4).
* fix(frontend): expose DrillPicker.onFilterChange + lazy-load workspace kinds
Both Codex P1s came from over-eager preload heuristics on my prior fixes:
the workspace picker cold-loaded every configured kind on mount in
internal-filter mode, and the chat badge popover never observed its own
internal filter so workspace results were missing from search until the
user drilled into Workspace.
Replace both ad-hoc effects with a single `onFilterChange` callback on
DrillPicker that fires whenever the EFFECTIVE filter (external or
internal) changes:
- [P1] WorkspaceItemDrillPicker: drop the "cold-load on mount when
externalFilter === undefined" effect. Workspace kinds now load only
once the user actually types something — closer to the pre-refactor
behavior where the breadcrumb / "Open editor" pickers only fetched
the drilled-into kind plus all kinds on search.
- [P1] ChatContextPicker: handleFilterChange replaces the prior
externalFilter-only effect. Badge-popover search (internal filter)
now triggers the same preload as inline-mention search (external
filter), so workspace results appear without needing to drill first.
Both fixes reported by Codex on PR #9159.
* fix(frontend): skip mention-removal sync when textarea is programmatically cleared
sendRequest() sets `instructions = ''` immediately after dispatching to
AIChatManager. The mention-removal effect treated this as user-initiated
deletion and cleared selectedContext BEFORE AIChatManager.beforeSend
snapshotted it — selected `@` contexts disappeared from the outgoing
request. Skip the sync when value is empty; user-initiated mention
deletes happen in-place against non-empty content.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(frontend): scope post-send wipe protection to the send path only
Replace the blanket `if (value !== '')` guard on the mention-removal
effect with an explicit `clearForSend()` export. `sendRequest()` now
calls it instead of `instructions = ''`, so a user manually clearing
the whole textarea still drops the corresponding context badges while
the post-dispatch programmatic wipe is silent.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(frontend): extract useWorkspaceItemsLoader composable shared by both drill picker adapters
WorkspaceItemDrillPicker and ChatContextPicker each duplicated the same
machinery: loaded/loadingKind state seeded from the module cache, a
stale-while-revalidate ensureLoaded coroutine with an untrack guard, a
kind:/dir: scope-segment decoder, and the "load every kind once the user
starts searching" filter callback.
Move that to a single useWorkspaceItemsLoader() returning
{loaded, loadingKind, ensureLoaded, ensureAll, ensureForScopeSegment,
onFilterChange}. Adapters keep their own scope-walking policy (chat
collapses an optional 'workspace' wrapper, workspace handles single-kind
mode) but delegate kind decoding and lazy fetch to the composable.
Net: -135 +28 LOC in the two adapters; +109 LOC in the new composable.
The cache-version race, untrack discipline, and stale-while-revalidate
semantics now live in one place.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(frontend): address Codex P1+P2s — non-context clear, same-title cross-removal, single-kind cold load
P1: sendRequest() now clears `instructions` unconditionally after the
optional `clearForSend()` so APP/NAVIGATOR/ASK/API modes (which don't
mount ContextTextarea) still reset the input after send.
P2: removeMention() now calls a new `unsyncMention(title)` on the
textarea before stripping `@title` from `value`, so the mention-removal
effect doesn't fire a second onRemoveContext on a same-title sibling
(e.g. workspace_script + workspace_flow sharing a path).
P2: single-kind WorkspaceItemDrillPicker loads its kind at mount even
when scope is empty — buildWorkspaceTree collapses to the kind's
children, so there's no kind row to drill into to trigger the load.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Operators are read-only and cannot create native triggers, yet the
google/github/nextcloud integration picker routes had no authorization
gate, letting any workspace member drive the admin-configured
integration's upstream API and enumerate its data (Drive files, repo
names, calendars, events).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: authenticate slack callback payload with per-workspace hmac
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: regression tests for unauthenticated slack callback decryption
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: verify slack submission signature before resume + close workspace oracle
Addresses review: verify private_metadata HMAC before handle_resume_action so a
tampered/unsigned submission is rejected up front, and map get_workspace_key
failure to the generic 401 so the status code is not a workspace-existence oracle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: domain-separate slack payload hmac from resume-secret hmac
Both MAC families key Hmac<Sha256> on the same per-workspace key; resume secrets
are distributed to approvers in resume URLs, so add a fixed domain tag
(slack_payload_v1) to the slack payload MAC to make the two non-interchangeable
by construction rather than by byte-layout coincidence.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A flow inline step whose id is a Python keyword (e.g. `in`) crashed with a
`SyntaxError`: the wrapper emits `from {pkg} import {step_id} as inner_script`,
and `from x import in as y` is invalid Python.
The codegen already prefixes `_` to path segments that start with a digit
(`1234` → `_1234`); this extends that guard to Python hard keywords (`in` →
`_in`) in `compute_python_module_dir` and on the leaf in `compute_py_codegen`
and `prepare_wrapper`. The relative-imports write path inherits it for free.
Fixes#8893
* fix: prevent token label collision bypassing job read access control
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: regression tests for token label collision job read access
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: bind job-read override fast-path to permissioned_as_email
Replaces the reserved-label / label-* exclusion approach: webhook-/http-/email-
labels are created through the public token API by the trigger panels, so they
cannot be reserved, and blocking label-* regressed legitimate re-reads. Instead
the username_override fast-path now requires the job's permissioned_as_email
(non-forgeable, never derived from the label) to equal the caller's email.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(otel): propagate inbound W3C traceparent to job spans
Capture the inbound traceparent header at the run endpoints
(WebhookArgs::to_args_from_format) into a reserved _wm_traceparent arg key
(gated on OTEL_TRACING_ENABLED), riding the args jsonb like
_ENTRYPOINT_OVERRIDE. At pickup, create_span_with_name attaches a span link
from the job's worker span to the originating distributed trace, so a job
triggered by an instrumented service is connected to the caller's trace
while keeping its UUID-derived trace id (trace-by-job-id unaffected).
The link/parse logic lives in the EE otel modules; this OSS side only
captures the header and calls the (no-op outside EE) hook. Companion EE PR
required.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref to inbound-trace-propagation EE branch
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(agents): don't attribute work to specific customers in repo content
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(otel): relocate job + script spans into the inbound trace
Builds on the captured _wm_traceparent: the worker job span is re-parented on
the inbound caller context, the script subprocess's TRACEPARENT env is the
inbound context (so its spans join the caller's trace), and the context is
propagated to flow steps so the whole flow relocates. Carried to the worker via
a new LogContext.inbound_traceparent field. Non-inbound jobs are unchanged.
Adds a relocation integration test. Companion EE PR required.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref to inbound-trace-propagation relocate commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(otel): harden inbound traceparent capture
Address review feedback:
- strip any caller-supplied _wm_traceparent from args/extra before stashing the
header-captured value, so the reserved key is Windmill-controlled only
- valid_w3c_traceparent: reject version ff and require lowercase hex, so we don't
forward an inbound header that downstream OTel parsers would reject
- clarify that the capture helper does not validate the W3C format (done at use)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 2c7964460327fab5e3a27c0f74b8d6f26ab7f79a
This commit updates the EE repository reference after PR #604 was merged in windmill-ee-private.
Previous ee-repo-ref: 8fc04fb105dc49769205f7174d551a0d134d1bec
New ee-repo-ref: 2c7964460327fab5e3a27c0f74b8d6f26ab7f79a
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: distinguish canceled jobs in runs
* fix: order status=failure|canceled by completed_at to use partial index
The new `status` query param replaced the legacy `success=false` filter on
the Runs page, but the ORDER BY switch in list_completed_jobs_query only
flipped to v2_job_completed.completed_at for success==Some(false). With
status=failure|canceled (and success=None), the query fell back to ordering
by v2_job.created_at, which the partial index
ix_v2_job_completed_failure_workspace (workspace_id, completed_at DESC WHERE
status IN ('failure','canceled')) cannot serve.
EXPLAIN ANALYZE on 500k rows (1% failure/canceled): ordering by completed_at
uses the partial index (~150 buffers, 0.3ms); ordering by created_at scans
the v2_job created_at index and probes/discards 99% of rows via the join
(~49k buffers, 31ms). Switch the ordering to completed_at for
failure/canceled so the partial index serves both filtering and ordering.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: trim order-by regression test to the failure/canceled case
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: only treat canceled as a terminal status icon for completed jobs
Guard the canceled branch in JobStatusIcon and getJobStatusKind with
`'success' in job` so a job that is still running while being canceled keeps
its running icon/favicon until it completes, instead of immediately showing
the gray Canceled state. Also clarify the openapi `status` param is an exact
match (status=success excludes skipped, unlike success=true).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(sandbox): pull/extract images with crane instead of podman (+ add to image)
The sandboxed container runtime (`# sandbox <image>`) only ever pulls + flattens an
image (nsjail does the run), so a full container engine is overkill — and podman was
never actually in any Dockerfile, so the merged feature couldn't run in the shipped
image. Switch to crane (google/go-containerregistry): a single ~25MB static binary,
no daemon/store/root/privileged.
- docker_v2.rs: crane export -> flattened rootfs tar, crane config -> OCI config,
crane digest -> content-addressed rootfs+config cache (cross-job dedup + automatic
freshness), crane manifest -> pre-download size guard. DOCKER_CONFIG authfile dir.
Cache eviction prunes the rootfs-tar cache by mtime (LRU). Pull policy honored via a
ref->digest cache (missing/never reuse without a registry hit).
- Dockerfile + docker/DockerfileSlim{,Ee}: install the crane binary (Full/FullEe and
the EE image inherit it via FROM the base image).
- docs + UI text + instance-setting descriptions updated (download size is compressed;
cache is the rootfs-tar cache).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(sandbox): address CI review — digest-pinned fetch, size cap on every job, eviction race
Codex P1s:
- Fetch by the resolved digest (name@digest), not the mutable tag, so content can't
diverge from the digest the cache is keyed under if a tag moves mid-fetch.
- Enforce the size cap on EVERY job via a cached {digest}.size sidecar (no registry call
on cache reuse), so lowering the limit rejects already-cached oversized images.
- Eviction race: hardlink the cache tar into the job dir before tar -xf (pins the inode
against concurrent eviction) and re-fetch if it was evicted first.
Claude P2s: atomic config sidecar (tmp+rename) + tolerate torn parse; soften the LRU
comment (mtime = creation order); sweep orphaned *.tmp.* and .size on eviction.
+digest_key/ref_key unit tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(sandbox): P1 cross-fs cache staging (EXDEV), Dockerfile arch fail-fast
CI re-review (Claude + Codex P1): the eviction-race hardlink crosses filesystems in the
shipped deployments — the cache is its own volume (/tmp/windmill/cache) while the job dir
is on the container fs — so hard_link returns EXDEV (not NotFound) and every sandbox job
fails. Fall back to tokio::fs::copy on a non-NotFound link error; copy reads through the
source inode so it still survives a concurrent eviction.
Also: Dockerfiles fail fast with a clear error on an unsupported arch instead of building
a 404 crane URL; ref->digest file written via tmp+rename (no torn read under missing/never).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(sandbox): say 'oldest by creation time' not 'LRU' for cache eviction
Codex P2: the code evicts by tar creation time (cache hits don't touch mtime), so the
user-facing docs + instance-setting text shouldn't claim true LRU.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: make C# dotnet target framework configurable via DOTNET_TARGET_FRAMEWORK
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: include dotnet target framework in C# binary cache key
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add sandboxed docker v2 runtime via '# docker <image>'
Run a container image as a subprogram of the job's own nsjail sandbox:
extract the image rootfs with podman (rootless) and run it chrooted inside the
job's nsjail, so the container inherits the job's confinement and is safe under
nsjail / for untrusted code. Selected by '# docker <image>'; a bare '# docker'
keeps the v1 (dind) path untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: default to daemonless docker (drop dind from compose, allow docker on cloud)
docker-compose no longer ships the dind sidecar (v2 is daemonless: podman + nsjail
in the worker); removed the dind service, DOCKER_HOST env, depends_on and volume.
Removed the language-picker guard that blocked Docker scripts on the multi-tenant
platform, now that v2 makes docker safe to run sandboxed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: select sandboxed container via # sandbox <image>; add pull policy + size guards
- Surface moved from '# docker <image>' to '# sandbox <image>' (groups under the
sandbox annotation; '# docker' stays v1-only, '# sandbox' stays nsjail-bash).
- SANDBOX_IMAGE_PULL_POLICY (default 'newer') so moving tags don't go stale.
- SANDBOX_IMAGE_MAX_SIZE_MB rejects oversized images before extraction.
- SANDBOX_IMAGE_CACHE_MAX_MB best-effort LRU eviction of podman's image store.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(sandbox): support # volume, honor nsjail tmp instance settings, v2 docker template
- Thread shared_mount into the sandbox container nsjail config so '# volume' mounts
(and the same-worker /tmp/shared folder) apply inside the container.
- Use resolve_nsjail_tmp_mount_block for the container's /tmp so it honors the same
nsjail_tmp_backing / nsjail_tmpfs_size_mb instance settings as other nsjail jobs.
- docker-compose comment + the editor's Docker template now use '# sandbox <image>'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(sandbox): make image size/cache/pull-policy UI instance settings
Convert SANDBOX_IMAGE_* from worker env vars to DB-backed instance settings
(sandbox_image_max_size_mb, sandbox_image_cache_max_mb, sandbox_image_pull_policy),
hot-reloaded via the same mechanism as nsjail_tmpfs_size_mb and configurable in
#superadmin-settings. No worker restart needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(sandbox): windmill-managed registry — default registry + private auth
Two new instance settings:
- sandbox_image_default_registry: prepended to unqualified image refs (alpine ->
<registry>/alpine); fully-qualified refs untouched.
- sandbox_registry_auth: docker/podman auth.json blob written to a per-job authfile
(0600, removed with the job) and passed to podman --authfile for private registries.
Both hot-reloaded and configurable in #superadmin-settings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(sandbox): protobuf-safe proto_str escaper, atomic 0600 authfile, registry tests
Addresses local-review P2s: proto_str now emits valid protobuf octal escapes for
control/non-ASCII bytes (not Rust \u{..} that nsjail would reject); the registry
authfile is created 0600 atomically (no world-readable window); add a
registry_qualified table test + a non-ASCII proto_str case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(sandbox): P0 — deliver image env via nsjail envar:, never the launcher process env
CI review (P0): the image's OCI Env (attacker-controlled keys+values) was applied to
the nsjail launcher process via .envs(), so a hostile image could set LD_PRELOAD/
LD_LIBRARY_PATH/LD_AUDIT on nsjail itself and execute code as the worker outside the
jail. Now the image env is rendered as proto-escaped 'envar:' directives (child-only)
and nsjail's process env carries only windmill-trusted keys (reserved vars + proxy).
Also: warn instead of silently bypassing the size guard on inspect failure; reset the
eviction guard via a Drop guard (no stuck flag on panic/early-return). +render_envars test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(sandbox): P0 symlink-write escape via rootfs script; P1 redact registry-auth logging
CI review:
- P0 (Codex): the body was written into the image-controlled rootfs as
.windmill_docker_main.sh via write_file (follows symlinks) — a hostile image could
plant that path as a symlink to a host file and capture the worker's write before
nsjail starts. Now the body is passed straight to 'sh -c <body> sh <args>'; no file
is written into the rootfs at all.
- P1 (Codex): sandbox_registry_auth flowed through the generic setting loader which
logs the value (raw auth.json credentials). Replaced with a secret-aware reload that
loads directly and logs only a redacted 'configured=' message.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(sandbox): redact sandbox_registry_auth in instance-settings write log too
The settings API also logs 'Set global setting <key> to <value>' via format_setting_value;
add sandbox_registry_auth to SENSITIVE_SETTINGS so the credential is redacted there as
well as on reload.
* fix(sandbox): don't silently disable cache eviction on podman images parse error
Re-review (cubic/Claude P2): serde_json::from_slice(...).unwrap_or_default() meant any
parse hiccup (e.g. podman omitting Size/Created via omitempty for a zero value, or
schema drift) silently degraded to an empty Vec and disabled eviction with no log.
Now Size/Created are #[serde(default)] (a missing omitempty key -> 0, not a whole-array
parse failure) and a real parse error warns + breaks instead of being swallowed.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ee repo ref
* fix(ee-ref): pin to EE commit that includes read_only create_session_token fix
The previous pin (f7a83d9) carried only the connect_config_template change and
dropped Ruben's read_only=false fix (EE 3742e06). CE #9371 made
create_session_token require 6 args, so the EE overlay fails check_ee_full with
an arity error without it. Bump the pin to 9be38de, which includes both fixes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to fb106b89cdf4088b004dac6062adb029f3923887
This commit updates the EE repository reference after PR #603 was merged in windmill-ee-private.
Previous ee-repo-ref: 9be38def879f702cd0b134d9e71bbb17fbb9cfa4
New ee-repo-ref: fb106b89cdf4088b004dac6062adb029f3923887
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Adds DatabricksIcon.svelte (brand mark, #FF3621) and registers it under
`databricks` in the shared APP_TO_ICON_COMPONENT map, so both the app and
hub frontends pick it up for the new Databricks hub integration.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Adds AdobeAcrobatSignIcon.svelte and registers `adobe_acrobat_sign` in
APP_TO_ICON_COMPONENT, for the Adobe Acrobat Sign hub integration
(windmill-labs/windmill-integrations#143).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* oauth: add ServiceNow provider; make per-instance OAuth registry-driven
ServiceNow's OAuth endpoints are per-instance
(https://<instance>.service-now.com/oauth_auth.do + /oauth_token.do), like
Snowflake's. Rather than add another bespoke special-case, generalize:
a registry entry may carry a `connect_config_template` (label/placeholder/
help_url + {instance}-templated auth_url/token_url + req_body_auth +
optional extra_params_key/strip_suffix). The instance-settings UI renders
one generic instance-name input for any such provider and substitutes
{instance} to build the per-client connect_config — a new per-instance
provider needs only a JSON entry, no frontend code.
- oauth_connect.json: servicenow + snowflake_oauth now carry a
connect_config_template (snowflake keeps its account_identifier
extra_params key for backward compatibility).
- windmill-oauth: add the ConnectConfigTemplate struct (frontend-only
metadata; the backend's existing connect_config override resolves the
concrete URLs generically — no other backend change).
- AuthSettings/InstanceSettings: replace the Snowflake + ServiceNow
special-cases with one registry-driven path (instanceInputs map,
setupTemplatedOauthUrls, loadInstanceInputs); per-instance providers are
derived from the registry for the builtins list + dropdown.
Pairs with windmill-integrations#139 (ServiceNow hub integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: point ee-repo-ref at servicenow-oauth EE branch (revert at merge)
Temporary CI pointer so check_ee_full / cargo_test build against the EE
slack-literal fix (windmill-ee-private#602). Revert to a pinned SHA once
that EE PR is merged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wiz star logomark (brand blue #0254EC) for the shared icon map
(APP_TO_ICON_COMPONENT), for windmill-integrations#144.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(flows): early stop can include the stopping step's result in the raised error
When a step uses Early Stop with "Raise an error message if stopped", the
flow result was entirely replaced with a static error object
({"error": {"name": "EarlyStopError", "message": "..."}}), discarding the
stopping step's own output. This made it impossible to stop+fail a flow
while preserving the data the step produced (e.g. an API that returns
HTTP 200 with a userErrors payload).
Add an opt-in `error_include_result` flag on StopAfterIf. When enabled on
the raise-error path, the raised payload becomes
{"error": {...}, "result": <step result>} instead of dropping the result.
Default is false, so existing behavior is unchanged. The option is threaded
through the worker's stop-after-if handling (including stop_after_all_iters_if
for loops/branchall) and exposed in the flow editor's Early Stop panel.
Fixes WIN-2012
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(flows): cover early-stop error_include_result payload shaping
Add a regression test asserting that a step using Early Stop with a raised
error message and error_include_result=true fails the flow while preserving
the step output as {"error": {..}, "result": <step result>}, and that with
the flag off the result is the bare {"error": {..}} object.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(flows): nest early-stop step result inside the error object
Embed the stopping step's result under `error.result` rather than as a
top-level sibling of `error`. This keeps the flow result shape as
`{ "error": { .. } }` — identical to a normal error — so consumers that
key off the top-level shape (single `error` key) keep working, while the
data is still preserved for those that look inside the error object.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(flows): always include the stopping step's result in early-stop errors
Drop the opt-in `error_include_result` gate. Since the step result is nested
inside the error object (`error.result`), the top-level result shape stays
`{ "error": .. }` — identical to a normal error — so consumers that detect or
parse failures by the top-level shape are unaffected. Gating it added schema
surface, plumbing, and a UI toggle for no real compatibility benefit.
Now, whenever a step early-stops with a raised error message, the flow fails
and the raised error embeds the stopping step's own result under
`error.result` (aggregated iteration results for loops/branchall). This
reverts the `StopAfterIf.error_include_result` field, its threading, the
OpenAPI/generated-client surface, and the editor toggle; the "Raise an error
message" tooltip now notes that the step result is included.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(flows): gate early-stop result inclusion behind opt-in flag
Re-introduce the per-step `error_include_result` flag (default off) instead
of always embedding the step result. Although nesting the result under
`error.result` keeps the result *shape* backward-compatible, it does not
address data exposure: a failed flow's result is propagated to synchronous
webhook callers, the flow's failure module, and the workspace/global error
handler (commonly a Slack/email/outbound-webhook notifier). Always including
the step output would surface previously-redacted intermediate data to all of
those sinks for every existing error-stop flow.
Gating keeps the existing behavior (bare `{ "error": .. }`) as the default and
only embeds `error.result` when the flow author explicitly opts in, matching
the original issue's intent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(flows): omit error_include_result when false; refresh generated prompts
- Add `skip_serializing_if = "is_false"` to `StopAfterIf.error_include_result`
so serialized flows are byte-identical when the flag is off. Fixes the
`flowmodule_serde` round-trip test (cargo_test) and avoids churn on existing
flows.
- Regenerate `system_prompts/auto-generated/` and `cli/src/guidance/skills.gen.ts`
for the new OpenFlow `error_include_result` property. Fixes check-freshness.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(flows): cover error_include_result for the loop "stop after all iters" path
Add a regression test for the stop_after_all_iters_if branch, where `nresult`
already holds the aggregated iteration results — confirming `error.result`
carries each iteration's output (distinct from the per-step fallback path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): push whole raw app instead of treating frontend files as scripts
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(cli): shorten raw-app handleFile comment
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: resolve workspace imports via /f/,/u/ not $f/,$u/ aliases
Keep the CLI managed tsconfig.wmill.json / `refresh tsconfig` / Deno
import-map QoL from #9378, but re-key it on the existing /f/,/u/ workspace
paths instead of the new $f/,$u/ specifiers. Verified /f/,/u/ resolves in
tsc, Bun, Deno, the in-app ATA editor, and the worker, so the $-prefixed
alias added no value. Drop the $f/,$u/ handling from the parser, dep-map,
deno_executor, bun loaders, ATA, relative_imports and monaco paths; revert
the windmill-parser-wasm-ts bump (1.714.0 -> 1.695.0). Also fold in the
cli/package-lock.json sync for the already-committed pg-gateway dependency.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: drop duplicate relative-path check and restore rustfmt formatting
Follow-up cleanups to the previous commit's full-file reverts, which
restored pre-#9378 state that main had since improved:
- relative_imports.ts: remove the redundant duplicate d.startsWith('/')
(pre-#9378 had it; #9378 had repurposed that line, so main has no dup).
- windmill-parser-ts/src/lib.rs: restore the multi-line new_source_file(...)
formatting required by backend/rustfmt.toml (the single-line revert would
fail `cargo fmt --check`). Now differs from main only by the $f//$u/ removal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: let flow AI chat create and edit sticky notes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: strengthen flow AI guidance to prefer groups for organizing flows
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: harden flow note validation (validate position/size, document color default and group acceptance)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: make AI-created free notes draggable by seeding default position and size
Free notes need explicit geometry to be draggable/resizable in the editor; UI-created notes always set position+size but agent-created notes omitted both, so they couldn't be moved until resized. Seed defaults in validateFlowNotes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`cli/src/utils/utils.ts` imported `VERSION` from `cli/src/main.ts`, while
`main.ts` transitively imports `utils.ts` (via `workspace.ts`). When a module
load order entered the graph through `workspace.ts -> utils.ts -> main.ts`,
`main.ts`'s top-level command tree ran while `workspace.ts` was still
mid-initialization, so the `workspace` binding was still in its temporal dead
zone at `.command("workspace", workspace)`:
ReferenceError: Cannot access 'workspace' before initialization
This surfaced as 56 failing CLI tests on Windows CI (the Windows runner's test
module-load order triggers the bad path; it reproduces on any platform via
`bun -e 'await import("./src/commands/workspace/workspace.ts")'`).
Move `VERSION` to `cli/src/core/constants.ts` (already the "minimal imports"
module), re-export it from `main.ts` for backwards compatibility, and have
`utils.ts` read it from `constants.ts` — eliminating the cycle. Release tooling
(`.github/change-versions*.sh`) is updated to rewrite the `VERSION` line in its
new location.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a dependency job for an app is triggered by a relative/workspace
import (e.g. an imported script was updated), handle_app_dependency_job
re-appended the version captured at job-creation time to the versions
array. On a git-sync/CLI push that deploys both the imported script and
the importing app in the same batch, the script's dependency job
snapshots the app's old version; the app push then creates a newer
version (uploading its bundle against that new version); finally the
relock runs and re-appends the old version, making it latest again.
For raw apps this is fatal: bundle_secret is computed from the latest
version, so the served HTML requests /apps_u/get_data/v/<secret>.{js,css}
for a version that has no stored bundle -> 404 and a white screen.
Manually redeploying fixes it until the next merge re-triggers the revert.
Two changes:
- Re-query the current latest version to relock (mirrors the flow
dependency handler, #8673), so we don't lock a stale snapshot.
- Guard the re-publish append with `versions[array_upper(...)] = $1` so
it is a single atomic, never-demoting statement: it can only re-append
the version that is already latest, never revert to an older one. A
relock never creates a new app_version, so there is never a version to
legitimately promote here.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: support $f/ and $u/ import path aliases for scripts
$f/ and $u/ are local-friendly aliases for the absolute workspace
import paths /f/ and /u/. Unlike the /-prefixed form (which local tools
treat as a filesystem-root path), the $-prefixed form is a bare specifier
that can be remapped via tsconfig paths / Deno import maps, so the same
import resolves on the Windmill worker and in a local editor.
- worker: recognize $f//$u/ in the Deno import map and both Bun loaders
- dep-map/parser: normalize $f/->f/, $u/->u/ for lockgen + dep tracking
- cli: emit $f/$u path aliases in generated tsconfig.json / deno.json
- frontend: ATA + Monaco paths resolve $f//$u/ type hints in the editor
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): split generated tsconfig into managed + user file with refresh command
Mirror the AGENTS.cli.md/AGENTS.md prompts model for the IDE tsconfig so the
recommended settings can evolve without ever clobbering user customizations:
- tsconfig.wmill.json: wmill-managed, always refreshed, holds recommended
compilerOptions incl. the $f/$u path aliases (Deno: import_map.wmill.json)
- tsconfig.json: user-owned, created once, just extends the managed file;
warn (never auto-edit) when an existing one doesn't reference it
- add 'wmill refresh tsconfig'; init generates it unconditionally (no longer
gated behind resource-type namespace / a bound workspace)
- regenerate CLI guidance docs for the new subcommand
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): address PR review on $f/ tsconfig generation
- handle existing deno.jsonc so we don't shadow it with a new deno.json
(P1 identified by cubic)
- fix the bun-types hint that pointed users at the managed do-not-edit
tsconfig.wmill.json; tell them to install + re-run 'wmill refresh tsconfig'
- document the .ts-extension-only local-resolution limitation (cross-flavor
.bun.ts/.deno.ts/.fetch.ts scripts won't resolve in a local editor)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): warn when a project's tsconfig isn't wired to tsconfig.wmill.json
Mirror the prompts freshness check for the managed tsconfig so users with an
existing setup actually discover they're missing $f//$u/ resolution:
- embed a version hash in tsconfig.wmill.json (excludes the env-dependent
bun-types 'types' entry so it doesn't false-positive)
- add warnIfTsconfigStale to the main.ts freshness hook, gated identically to
the prompts check (skips init/refresh/help/version). When a tsconfig.json
exists it warns one line (stderr) if the managed file is missing, not
referenced via extends, or out of date; silent for non-TS projects.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(cli): make tsconfig setup equivalent to prompts (auto-wire + stale-only)
Unify the two managed-file systems so they behave identically:
- auto-wire an existing unlinked tsconfig.json/deno.json on init/refresh
(add extends / importMap; merge into an array extends), instead of only
warning. Parses JSON and falls back to a warning when it can't round-trip
(JSONC comments, or a conflicting deno imports/importMap) — never corrupts.
- narrow warnIfTsconfigStale to stale-only, gated on the managed file
existing, exactly like warnIfPromptsStale: it no longer nags about a
missing or unlinked tsconfig.json, so a deliberately-custom/unlinked setup
stays silent and a not-yet-initialized project isn't bothered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): place tsconfig.wmill.json first in extends to preserve user base config
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): migrate legacy tsconfig and require consent for custom configs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(cli): align prompts wiring to the same consent model
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(cli): bump windmill-parser-wasm-ts to 1.714.0 for $f/ $u/ aliases
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(worker): resolve $f/ and $u/ in deno lock generation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: narrow relative-imports lock-gen guard to deno import-map failure
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(cli): sync bun.lock with windmill-parser-wasm-ts 1.714.0
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): warn when a custom tsconfig's paths would shadow $f/ $u/ aliases
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: add datatable tool coverage to global ai_evals (stage 0+1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: add seeded datatable difficulty-ladder global ai_evals (stage 2)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: skipJudge datatable evals and make stringIncludesAnyOf existential
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: make ai_evals datatable mock reflect SQL writes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: gate workspace fork creation in sessions behind enterprise license
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: gate session fork creation on CE workspace cap, not EE license
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The variable and resource value caches (backing
`GET /api/w/{w}/variables/get_value/{path}?allow_cache=true` and
`.../resources/get_value_interpolated/{path}?allow_cache=true`) are consulted
before the per-folder RLS query and store the already-decrypted value. The
resource cache was keyed only by `workspace:path` with no caller identity, so a
cache entry warmed by a privileged peer using `allow_cache=true` could be
returned to a caller with no access to the resource's folder on a cache hit
within the 30s TTL — leaking another folder's decrypted secrets.
Scope both caches to the caller's full authorization identity. The key is now
`auth_identity(authed):workspace:path`, where `auth_identity` is a SHA-256 of the
caller's effective authorization context (email, username, is_admin, is_operator,
sorted groups, sorted folders, sorted scopes) — mirroring
`job_read_access_cache_key`. Email alone is insufficient: the same email can
resolve to different effective permissions via job/owner-scoped tokens, so a
lower-privilege context must not reuse a higher-privilege context's entry.
Job-context resource interpolation is handled correctly: only `$WM_*` contextual
variables are resolved (and only when a `job_id` is present). The interpolation
reports whether the value contains a `$WM_*` placeholder
(`transform_json_value_tracked` + an `AtomicBool`). A value containing one is
job-dependent — even on a no-job read where it's left unresolved — and is never
cached (so a later job read never gets a stale placeholder or another job's
context). Any value without a `$WM_*` placeholder is job-independent and cached
under the identity key, shared across job contexts, so reads carrying a `job_id`
still hit the cache.
BEHAVIOR CHANGE: custom workspace environment variables are no longer interpolated
into resource values via `$NAME` (this was undocumented and prevented caching of
any `$`-prefixed value). Custom envs remain available to scripts/workers as before.
Built-in `$WM_*` contextual variables in resource values are unchanged.
The variable cache previously wrote with an identity-scoped key but read with the
unscoped key, so it never hit (a latent functional bug that happened to be safe).
Aligning the read path enables the cache and makes it identity-scoped by
construction. Secret variables are cached too, but the entry carries the
`is_secret` flag so a cache hit re-runs the per-read side effects a secret read
performs — the EE `variables.decrypt_secret` audit and running-job secret
registration (factored into `audit_decrypt_secret`, shared by both paths).
The unused `invalidate_{variable,resource}_cache` helpers can no longer target
identity-scoped entries; documented the constraint and refreshed the stale
key-format docs on the cache statics.
Tests:
- integration regression for both caches: a folder-scoped user warms the cache via
allow_cache=true, then a user without folder access is denied (401) and never
receives the cached value.
- integration regression that variables (secret included) are served from cache.
- integration regression for job context: plain and non-`$WM_` `$`-string resources
stay cached and are served under a job_id, while a `$WM_*` resource (warmed without
a job_id) is not cached.
- unit tests for `auth_identity`.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(mcp): resolve MCP resource token via caller RLS + SSRF-guard url
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(mcp): clone user_db for oauth2 refresh and drop advisory ids from comments
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(mcp): disable redirects on MCP client to prevent SSRF bypass
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A token scoped to a single script or flow path (e.g.
`scripts:read:f/allowed/*`) could call `GET .../scripts/list_search` (or
`/list`) and receive `path` + full `content` for every script the
underlying user could see — likewise `flows/list_search` leaked the full
flow `value`. Route-level scope checks only validate `domain:action`, and
the listing handlers did no per-row scope filtering, leaking out-of-scope
source/definitions to narrowly-scoped tokens.
Apply `build_scope_path_predicate` (added in #9302 for resources/variables)
to `list_search_scripts`, `list_scripts`, `list_search_flows`, and
`list_flows`, mirroring the resources/variables fix exactly. Unscoped
tokens and tokens whose only scopes are `if_jobs:filter_tags:*` are
unaffected.
Adds integration regression tests (scripts + flows) covering: path-scoped
token sees only in-scope paths, broad `*:read` token still sees all
RLS-visible items, tag-filter-only and unscoped tokens unchanged.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(backend): authorize single-job read endpoints by job/flow visibility
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(jobs): share read links + cached access checks for run visibility
- Cache the job read-access RLS probe (size-bounded LRU keyed by the caller's
authz-relevant identity + job id; no TTL since job-side inputs are immutable).
- Inherit visibility along the full parent_job chain so any flow you can see lets
you read its (deeply nested) steps.
- Share read links: GET /jobs/job_view_token/{id} mints a stateless
HMAC(workspace_key, job_id) token (only if the caller can read the job); the
token grants an authenticated member read of that job and its flow subtree via a
?view_token query param or X-View-Token header. Run page gains a Share button and
honors a ?view_token link.
- Denied-but-existing reads now return 403 with guidance to request a share link
(vs 404 for non-existent), and the run page renders that case with instructions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(jobs): address PR review — scope-tag check on mint, constant-time view-token verify
- P1 (Codex): get_job_view_token now enforces the caller's if_jobs:filter_tags
scope before minting, so a tag-scoped token can't mint a transferable link for a
job outside its tags. Adds a scoped-token regression test (allowed + denied).
- Constant-time view-token verification (HmacSha256::verify_slice) instead of
comparing hex strings (Claude/Pi nit).
- get_completed_job_result: an authed reader passing an invalid suspended-secret
triple now falls through to the normal visibility gate instead of erroring out
(Claude nit); unauthenticated callers still rejected.
- Length-prefix the read-access cache key fields so no input values can collide.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(api): add job_view_token to openapi spec; use generated client in run page
Addresses Codex review nit: the new GET /jobs/job_view_token/{id} endpoint was
missing from openapi.yaml (the source the frontend client is generated from). Adds
the path + operationId getJobViewToken, and switches the run page's Share button
from a raw fetch to JobService.getJobViewToken.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): carry view_token on share-link downloads
Addresses Codex review: download actions bypass the request interceptor that adds
X-View-Token (downloadViaClient uses raw fetch; cookie-mode downloads use plain
hrefs), so a share-link viewer got 403 downloading logs/results/args. Append the
view_token query param to the job download paths (result/logs/args/flow-all-logs)
via a new appendViewToken() helper, covering both client-fetch and href modes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(jobs): enforce tag scope in require_job_read_access (view-token use side)
Addresses Codex P1: the view_token use-side bypassed if_jobs:filter_tags on
handlers that don't tag-filter their data query (result_by_id,
get_flow_job_debug_info, get_otel_traces) — a tag-scoped token could use someone
else's valid share token to read out-of-scope job data. Move the tag-scope check
into require_job_read_access (runs before any created_by/view_token/RLS grant), so
it applies uniformly to every gated handler; removes the now-redundant explicit
check in get_job_view_token. Adds a use-side regression test (scoped token + valid
out-of-scope view_token denied on otel/result_by_id; in-scope still allowed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): include workspace in share read link
Addresses Codex P1: the copied share URL omitted the workspace. The token is
signed with the run's workspace key and the logged layout only switches
$workspaceStore when the URL carries workspace=, so a recipient whose persisted
active workspace differs would open the link against the wrong workspace and the
token would fail validation. Pin workspace= alongside view_token in the link.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(jobs): authorize get_result_maybe get_started branch for queued jobs
Addresses Codex P1: get_completed_job_result_maybe only gated when a completed row
existed; with ?get_started=true a non-reader reached the fallback branch and got
started:true for a running private job. Now fetches created_by and authorizes
(created_by/view_token/RLS, or anonymous for unauth) before disclosing
running-state; a non-existent job still returns started:false (leaks nothing).
Adds a regression test with a queued (no completed row) private job.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Swap the final-stage nsjail dependencies (libprotobuf-dev,
libnl-route-3-dev) for their runtime-only counterparts (libprotobuf32,
libnl-route-3-200, libnl-3-200) across the root Dockerfile, DockerfileSlim,
and DockerfileSlimEe. nsjail is already compiled in the build stage, so the
final image only needs the runtime shared libraries. This shrinks the
images and reduces CVE scan noise from unused dev packages.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): harmonize diff button placement across editors
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(frontend): address review nits — drop unused diffDrawer param, fix stale comments
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Zoom URL-validation challenge handler in `handle_challenge_request`
would HMAC-sign any arbitrary `plainToken` and return the result. Since
Zoom webhook verification checks `HMAC-SHA256(secret, "v0:{ts}:{body}")`,
an attacker could craft a `plainToken` in that format to obtain a valid
signature for a forged body, bypassing authentication on a later request.
Unlike the Twitch handler, the Zoom handler verifies no signature on the
challenge request (Zoom's protocol does not include one). Reject any
`plainToken` containing `:` or longer than 128 chars: legitimate Zoom
validation tokens are short random hex strings that never contain colons,
while the exploit requires the colon-bearing `v0:{ts}:{body}` format.
Fixes WIN-2008
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#9393 added `--compile-bytecode` to the uv pip install run inside the
python download nsjail. uv spawns a Python interpreter that compiles .py
files with parallelism scaling to the host CPU count, opening many file
descriptors at once. The download nsjail capped `rlimit_nofile` at 64,
which is exhausted on high-core machines, failing every install with
"Failed to bytecode-compile ... Too many open files (os error 24)".
Low-core VMs never hit the cap, so this surfaced only as a regression on
larger workers after upgrading.
Raise `rlimit_nofile` to 10000, matching the runtime configs
(run.python3 / run.ansible) that already use that value.
Fixes WIN-2009
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(backend): route //native TypeScript previews to native workers
Previewing a TypeScript script carrying the `//native` annotation was
pushed with `language = bun` (what the editor sends), so the job was
tagged `bun` and routed to a regular bun worker. A native-mode worker
neither matches the `bun` tag nor accepts a non-native `script_lang`
(worker.rs rejects with "cannot execute non-native job with language
'bun'"), so previewing a `//native` script on a native-only worker setup
failed — even though the deployed version of the same script runs fine
as `bunnative` / tag `nativets`.
`push` now reconciles the preview language with the `//native`
annotation for `JobPayload::Code`, mirroring the deploy-time logic in
`worker_lockfiles`: `bun` + `//native` is promoted to `bunnative` (tag
`nativets`), and `bunnative` without `//native` is demoted back to
`bun`. This makes a preview run exactly like the deployed script would,
and covers every preview entry point (run_preview_script, inline
preview, codebase preview) since they all go through `JobPayload::Code`.
Adds regression tests asserting the queued job's `script_lang`/`tag` for
all four (declared language × annotation) combinations.
Fixes WIN-2007
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(backend): add sqlx cache for preview_native_tag test query
The regression test's `sqlx::query!` for `v2_job` (tag, script_lang) needs
a cached entry so `SQLX_OFFLINE=true` CI compiles it. Adds exactly one new
cache file; no existing (OSS or EE) caches removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(backend): trim preview native-tag tests to the essentials
Keep the core regression (bun + //native → bunnative/nativets) and the
guard that plain bun previews are unaffected. Drop the two bunnative-
declared cases, which only re-verified the mirrored demote logic and
weren't the reported issue. The shared query is unchanged, so the sqlx
cache stays valid.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Refresh slim runtime packages
* fix(docker): bump pre-baked python to 3.12.12, drop redundant pip/setuptools upgrade
Align the slim images' pre-baked uv-managed Python with the backend default
(PyVAlias::Py312), which previously requested 3.12 while the image baked 3.11.10
— a minor mismatch that made the pre-bake unusable (every default job re-downloaded
3.12 at runtime).
Pinning 3.12.12 (latest 3.12 in uv's list) also drops bundled setuptools entirely
and ships current pip via python-build-standalone, so the explicit
`uv pip install --upgrade pip setuptools` step is now redundant and removed.
Also remove the dead PYTHON_IMAGE ARG from RHEL8/RHEL9 Dockerfiles (declared but
never referenced in any FROM stage).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): stop git-sync promotion deploys from dropping triggers/schedules
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: bump git-sync hub script to hub/28261 (windmill-cli 1.713.2)
Points LATEST_GIT_SYNC_SCRIPT_PATH at the republished sync-script-to-git-repo
that pins windmill-cli@1.713.2, which carries the promotion include-derivation
fix in this PR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
When encryption_key.yaml changes and is pushed via `wmill sync push`,
pushWorkspaceKey prompted interactively to confirm re-encrypting the
remote secrets with the new key. That prompt ignored `--yes` and had no
TTY guard, so a CI/non-interactive push that included the key would
block (or behave undefinedly) on the prompt.
Thread a key-push options object (non-interactive flag + explicit
re-encryption choice) through pushObj into pushWorkspaceKey:
- Non-interactive (`--yes` or no TTY) and no explicit choice: skip the
prompt and default to re-encrypting all remote secrets with the new
key (matches the interactive default), preserving their plaintext
values.
- New `--skip-reencrypt-on-key-change` flag (and the
WMILL_NO_REENCRYPT_ON_KEY_CHANGE=true env var for CI) opt out of
re-encryption — only safe when the remote ciphertexts are already
encrypted with the new key (e.g. workspace/instance migration).
- Interactive behavior (TTY, no `--yes`) is unchanged.
Regenerates system_prompts for the new option and adds unit tests for
the no-op, re-encrypt-by-default, flag-skip, and env-skip paths.
Fixes WIN-2005
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#9400 (WIN-2003) added the ctrl_break() handler as a `#[cfg(windows)]`
branch inside the two Windows-path `tokio::select!` blocks in
shutdown_signal. tokio's `select!` macro does not accept `#[cfg(...)]`
attributes on individual branches, so windmill-common fails to compile
on Windows ("no rules expected this token in macro call").
This slipped through CI because the only job that builds the backend on
Windows is cli-tests.yml's `test-windows`, which triggers only on
`cli/**` changes — #9400 was backend-only.
Fix: define `ctrl_break()` for the whole `not(any(linux, macos))` scope
instead of just `windows`. On Windows it awaits the real CTRL_BREAK
signal; on other non-unix targets it is a never-resolving future, so the
branch is inert there. The select! branches become plain (no per-branch
`#[cfg]`), which the macro accepts.
Verified: the `#[cfg]`-on-branch form reproduces the exact macro error
against tokio 1.46.1, and the fixed form compiles clean.
Fixes WIN-2003 (Windows build regression)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The instance username-conflict resolver rewrote
workspace_runnable_dependencies.app_path to the new user path before the
app row itself was renamed, violating fk_workspace_runnable_dependencies_app_path.
That FK is ON UPDATE CASCADE, so renaming the app already propagates the new
path; the manual rewrite was redundant and mis-ordered. Any user owning an app
under u/<username>/ with a tracked runnable dependency hit HTTP 500 and could
not have their username conflict resolved.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
On Windows, shutdown_signal only registered ctrl_c() (CTRL_C_EVENT).
CTRL_BREAK_EVENT — the default kill signal sent by Nomad's raw_exec
driver on Windows — had no handler, so the worker terminated
immediately without graceful shutdown, interrupting running jobs.
Add a ctrl_break() helper (mirroring the Unix terminate() helper) and
register it as an additional branch in both Windows tokio::select!
blocks in shutdown_signal.
Fixes WIN-2003
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): align Monaco editor font size with text-xs across viewports
* fix(frontend): make placeholder lineHeight reactive to fontSize
* fix(frontend): align GraphQL schema viewer font size with text-xs
The read-only GraphQL schema viewer was the lone Monaco instance still
inheriting Monaco's 14px default. Wire it through editorFontSize like
the other editors so it stays in sync with text-xs across viewports.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* oauth: add salesforce provider
Register Salesforce OAuth (Authorization Code) for Windmill resource connect.
Production uses login.salesforce.com; the sandbox block points at
test.salesforce.com (URL overrides only; scopes inherited) per #9358, so a single
canonical `salesforce` resource type covers both with separate `salesforce_sandbox`
instance credentials.
Paired with the hub integration: windmill-labs/windmill-integrations#131.
The Salesforce icon already exists in the frontend (SalesforceIcon.svelte).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix JSON syntax error in oauth_connect.json
* fix: add salesforce production tile to OAuth settings dropdown
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: refine ask-user-question chat display and keyboard nav
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style: use text-accent for ask-user-question icon
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: focus active choice when clicking ask-user-question card
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: disable chat input while an ask-user-question is pending
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: focus active choice on card click instead of pointerdown
Preserves text selection on the question card; wired as a use: action so the non-interactive card needs no keyboard handler.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: extract isActiveUserQuestion shared predicate
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: cover isActiveUserQuestion predicate
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add workspace datatable tools to global AI chat mode
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: cover global-mode datatable tools pure logic
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: expose datatable SQL SDK reference via get_instructions in global mode
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: make datatable get_instructions language-aware, default TypeScript
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: drop datatable/whitelist args from global init_app tool
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: flag missing datatable config as an explicit blocking error in global mode
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: drop dead branch in exec_datatable_sql result handling
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Python jobs under nsjail experience slow imports (~465ms for `import
requests`) because dependency directories are mounted read-only. Without
pre-compiled `.pyc` files, Python recompiles `.py` source to bytecode
in-memory on every import in every fresh nsjail process, paying the cost
repeatedly.
Add `--compile-bytecode` to both `uv pip install` invocations (the
Rust-driven install in python_executor.rs and the nsjail
download_deps.py.sh script) so `.pyc` files are generated at install
time and available at runtime even through read-only mounts. The files
are included in both the local cache and S3 piptar uploads.
The flag is supported by the uv version (0.9.24+) shipped in the
Dockerfile. The existing `__pycache__` skip only filters top-level dirs
for dedup logic, not subdirs within packages, so there is no conflict.
Fixes WIN-2001
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add global ai chat test tools
* fix: avoid session id in flow test preview
* test: cover global flow preview ids
* test: require script and flow test tools
* fix: harden global flow test fallback
* Revert "fix: harden global flow test fallback"
This reverts commit 97254ef33a.
* fix: fallback from inactive flow test hook
* fix: list nested flow steps in errors
* feat(frontend): surface local drafts in drawer editors with an unsaved-changes banner
Drawer-based editors (the 11 trigger types, plus resource and variable)
restore unsaved edits from browser localStorage on open using the same
mechanism as flows/scripts, but only showed a transient "Reset to deployed"
toast with no way to review the diff.
Add a persistent "You have unsaved changes" banner below the drawer header
with Show diff / Discard actions, shown whenever the form diverges from the
deployed baseline. Replaces the toast for these editors; flows/scripts/apps
(full-page) keep their existing toast.
- new shared LocalDraftBanner.svelte (Alert-styled bar + DiffDrawer)
- DrawerContent: optional `banner` snippet rendered below the header
- useTriggerDraftSync: reactive `hasDraft`, `deployed`/`current` getters and
`resetToDeployed`; drop the restore toast (banner supersedes it)
- wire the banner into all 11 trigger editors + variable; resource lifts its
dirty state up to ResourceEditorDrawer via a callback + accessors
- fix ScheduleEditorInner.openNew not resetting initialConfig (reused editor
instance kept a stale baseline, wrongly flagging a new schedule dirty)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): address PR review on local-draft drawer banners
- LocalDraftBanner: snapshot diff sides at click time so the diff drawer
doesn't keep updating as the user types behind it.
- VariableEditor / ResourceEditor: scope the banner and its Discard action
to the selected workspace; the cross-workspace dirty state stays surfaced
by the existing otherDirty Alert. Forward can_write via a new
onCanWriteChange callback so the resource banner hides Discard in
read-only mode (matching the trigger editors).
- useTriggerDraftSync: drop the now-unused path arg from maybeRestore and
update all 11 trigger editor call sites.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(frontend): deep-clone fallback in UserDraft.discard to avoid baseline aliasing
When a caller passed a live $state proxy as `fallback` (Variable/Resource
editors handed `initialStates[selected]` to the banner's Discard), the
handle's draft cell ended up sharing the same proxy as the caller's
baseline. Subsequent form edits mutated both sides in lock-step and the
dirty check kept reporting equal, so the banner never reappeared and
the Update button stayed disabled until the drawer was reopened.
Cloning the fallback inside `discard` (via `snapshotDraftValue`) gives
the handle a fresh tree and decouples the two reactive graphs. Trigger
editors already cloned at their call site (resetToDeployed); this just
makes the API self-contained for all callers.
Also switch the variable form's "Audit log for each access" alert from
warning to info — it's informational, not a warning.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(frontend): honor disabled prop in LocalDraftBanner's diff drawer
The banner's `disabled` prop hid the inline Discard button but the diff
drawer's "Discard changes" action was still wired unconditionally, so a
read-only user could bypass the hidden inline action via Show diff.
Gate the diff-drawer button on the same flag so both surfaces agree.
Flagged by cubic and Codex on PR #9335.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The flow AI chat's set_flow_json tool lets the model set a `color` on each
semantic flow group, but nothing told it which colors are valid, so it would
sometimes emit hex codes / arbitrary CSS color names. Those render with
default styling at best and break the group color picker at worst.
- core.ts: the set_flow_json schema `.describe()` and the `groups` system-prompt
bullet now spell out that `color` MUST be one of the palette names
(yellow, blue, green, purple, pink, orange, red, cyan, lime, gray) — no hex,
no CSS colors — and that omitting it lets the editor auto-assign one.
- helperUtils.ts: validateFlowGroups now rejects any color outside that palette,
sourced from the NoteColor enum so the two can't drift.
- helperUtils.test.ts: tests for reject-unknown / accept-known / accept-omitted.
Split out of the sessions branch (gl/layout-ai), where it had been bundled
into the large feature commit; it's an independent flow-AI improvement.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(sessions): chat + editor side-by-side with multi-session state
Introduces the Sessions feature: a workspace where the AI chat and an
editor (flow / script / app / raw-app) sit side-by-side, with each session
having its own AIChatManager instance, history, and target item. Sessions
are persisted across reloads and can be staged into forks for review.
Key pieces:
- sessions/ — SessionWrapper (the split-pane shell), SessionPicker
(sidebar list), SessionForkBar, SessionWorkspaceBar, FlowEditorView /
ScriptEditorView / AppEditorView / RawAppEditorView, ForkDiffDrawer,
sessionRuntime (per-session AIChatManager + draft state),
sessionState (in-memory + persisted index), sessionUnread, sessionScope,
appDraftCodec / flowDraftCodec, forkEditUrl, /sessions route.
- WorkspaceItemDrillPicker refactor — extracts WorkspaceItemRow + adds
surfaceAI drafts, stale-while-revalidate. workspacePicker.ts drops
explicit invalidate() in favor of always re-fetching in the background.
- ForkDiffDrawer + WorkspaceItemDiffViewer — per-kind diff bodies
reusable from the compare page. FlowGraphDiffViewer / FlowGraphV2 gain
inlineDiff forwarding + onHeight callback for equal-height layout.
- Global AI chat sessions plumbing — AIChatManager exports the class +
adds disabledModes, beforeSend hook, scoped instance context. AIChat /
AIChatDisplay accept session-only props (wideLayout, emptyHint,
inputPreface, hideHeader, hideModeSelector, forceDisabled). Chat
preserved across /flows/add → /flows/edit, /scripts/add → /scripts/edit.
- Draft-first loaders — sessions open drafts when present, otherwise
seed a draft from the last deployed value via globalDraftStore.
RawAppEditor / AppEditor / AppEditorHeaderDeploy get newApp prop +
fixes so draft-only apps can deploy.
- Compare page (/forks/compare) — bigger overhaul to plug into the new
drawer.
- Sidebar — Sessions entry + unread badge + status dot in
SidebarContent / MenuButton / SideBarNotification.
- Misc fixes — chat group color palette constraint, deploy_workspace_item
confirmation dropped, open_preview tool, picker drafts surfacing,
fork archive/delete buttons on compare page.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): bypass UserDraft inside session panes + sessionUnread crash
After merging main's UserDraft PR (#9121) into the sessions branch, two
integration issues surfaced:
1. AppEditor.svelte calls `UserDraft.use<App>('app', path)` at the
component level — keyed by ($workspaceStore, 'app', path). Sessions
that haven't materialized a fork yet stay at the user's main
workspace, so a session targeting an app at the same path as a
regular /apps/edit tab shared the same LS key. The session would
read the regular tab's autosave and write its fork-edits back over
it.
Gate UserDraft.use on `!getContext('aiChatManager')` — sessions
inject the manager via setContext, so inside a session pane the
handle is `undefined`, stateApp falls through to the `app` prop
the session loaded, and the auto-save $effect bails. Same gate on
the four UserDraft.remove call sites in AppEditorHeader and
RawAppEditorHeader so save/deploy from a session pane doesn't wipe
the LS draft of a non-session tab at the same path.
2. sessionUnread.svelte.ts called useLocalStorageValue at module
scope. Main's PR added a deep-mutation $effect inside that helper,
which now requires component-initialization context — every page
crashed at import time with `Svelte error: effect_orphan`.
Replaced with a plain module-level $state + manual localStorage
persist; same reactivity contract for callers.
3. ScriptEditorView.svelte was passing a `replaceStateFn` prop that
ScriptBuilder dropped on main. Removed.
Verified end-to-end with Playwright:
- /flows/edit/{path} regression: UserDraft handle still created, no
console errors
- /sessions loads, sessionUnread doesn't crash
- Session targeting non-raw app `u/admin/userdraft_collision_test`
displays the fork content (FORK_ONLY_MARKER) even with an LS
poison at `userdraft/w/local/app/{path}` containing a
POISONED_BY_REGULAR_TAB_AUTOSAVE marker; poison remains untouched
after the session loads and renders
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): stop fork-create retry loop on first user message
Removed the SessionWrapper $effect that retroactively committed the
session's workspace from the in-memory chat history. When opening a
session whose previous commit attempt had failed (or whose response was
lost) the effect ran in a tight retry loop, flooding the user with
`workspace_pkey` violations from `create_workspace_fork`.
The send path already commits through `AIChatManager.beforeSend` →
`commitSessionWorkspace`, which is the deterministic moment-of-action.
The $effect was a redundant reactive bridge that turned every backend
failure into an infinite retry.
Also hardens `materializeFork`/`commitSessionWorkspace` so the most
common cause of the duplicate-key error self-heals:
- `materializeFork` short-circuits when `fork.id` is already in
`$userWorkspaces` (the previous create actually succeeded, we just
lost the response). On a `workspace_pkey` catch, refresh the workspace
list and adopt the existing row instead of toasting an error.
- On a real `materializeFork` failure, `commitSessionWorkspace` now
drops `pending_fork` so the session falls through to the
workspace-pick fallback instead of looping on the same broken intent.
* feat(sessions): show EditorHeader breadcrumb in the not-found state
When a session's target item has been deleted or moved, the editor pane
used to render a bare "Script not found at path X" line — leaving the
user with no way to navigate to a different target without backing out
of the session.
Each editor view now renders a `SessionItemNotFound` shell instead: a
real `EditorHeader` (read-only summary, no pen popover) with a
breadcrumb keyed to the missing kind+path, plus the "not found" copy
below. Clicking any breadcrumb segment opens the workspace picker
scoped to that level — pick a replacement and the session swaps target
via the existing `onNavigate` callback.
`SessionItemNotFound` maps `raw_app` to `EditorHeader`'s `kind: 'app'
+ raw_app: true` so the picker routes through `/apps_raw/...`; the
local label still says "Raw app not found" (not "App not found") so
the user knows which surface is missing.
* fix(picker): stop self-feeding fetch effect that OOM'd the tab
The drill picker's $effect watched `scope` and called `ensureLoaded`
on every change. `ensureLoaded` reads `loaded[kind]` synchronously
(to decide whether to show a spinner), so the effect ended up
subscribed to the very signal it fills. Each fetch result wrote
`loaded[kind] = items`; Svelte 5's $state proxy notifies on every
property set even when the reference is unchanged from cache, which
refired the effect, which called `ensureLoaded` again, which awaited
the cached fetch, which wrote `loaded[kind]` again... runaway loop.
In `/scripts/edit/...` the picker's lifecycle stabilised quickly
enough to mask the loop, but in a session pane (multiple warm
sessions, picker kept alive by the surrounding state) the cycle
spun freely — 29.8 million iterations in <100 ms during testing,
enough to OOM Firefox / kill the Chromium tab.
Two changes:
- Replace the scope-watching $effect with an explicit `setScope()`
helper called from `drill()`, `goUp()`, and `onMount`. Fetch is
now a callback reaction to user navigation, never a reactive
consequence of one. No closed feedback cycle is possible.
- Untrack the `loaded[kind]` read inside `ensureLoaded`. The search
$effect (which loads every kind on first keystroke) is still a
reactive caller; the untrack stops it from subscribing to the
signal `ensureLoaded` fills, so the same loop can't form there.
* feat(script-editor): wire initialTestPanelCollapsed through ScriptBuilder
The `initialTestPanelCollapsed` prop was already declared on
`ScriptBuilderProps` (used by the session preview to start the editor
with the run/test pane closed) but never destructured in
`ScriptBuilder.svelte`, so the value silently dropped on the floor
and the test pane always opened.
- `ScriptBuilder.svelte` — destructure the prop and forward it to
`<ScriptEditor>`.
- `ScriptEditor.svelte` — accept the prop and seed `rawTestPanelSize`
to 0 when true, while keeping `storedTestPanelSize` at the default
30 so the user's first toggle expands the pane to a sensible width
rather than 0.
Regular `/scripts/edit/...` doesn't pass the prop → default `false`
→ panel still opens by default.
* fix(sessions): resolve aiChatManager via context in AskUserQuestionDisplay
Inside a session the chat uses a per-pane AIChatManager injected via context. AskUserQuestionDisplay imported the global singleton, so answers clicked in a session dispatched to the singleton's callback map and the AI loop stalled. Resolve via getContext with singleton fallback, matching ChatMode / ToolExecutionDisplay.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(raw_apps): let preview start in single-view on the preview tab
Add a defaultSplitWithPreview prop (default true). When false (session preview), the editor boots in single view with the preview tab selected: gate the onMount default-file activation, the setActiveDocument auto-activation, and iframeShouldMount so the UI Builder bundler iframe still mounts when preview is the active tab. RawAppEditorView passes defaultSplitWithPreview={false}.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(copilot): add get_preview_status tool and make open_preview idempotent
So the assistant can tell whether the session preview already shows the item it just edited, instead of re-opening or re-offering it. Mirrors the open_preview handler plumbing (setGetPreviewStatusHandler) and the session runtime registers it alongside open_preview. open_preview now returns 'already open' when the requested target matches the active session's current target. The system prompt steers the AI to check status before offering. Unit tests cover the no-arg schema, the session-only error, and handler dispatch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): make script preview reactive to AI draft writes
ScriptEditorView read the draft via static UserDraft.get inside an effect, which only subscribes to UserDraft's reactive cell when a live entry exists. None did for the preview path, so the chat's writes (UserDraft.save) only touched localStorage and the open preview never updated. Hold a live handle via UserDraft.useMany (reactive getter so it re-acquires when open_preview swaps the path without remounting) and read inbound through handle.draft, materializing the shared $state cell that bridges the chat's writes to the editor.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): make raw-app preview reactive to AI draft writes
Mirror of the script-preview fix. RawAppEditorView read the draft via static UserDraft.get inside an effect, which only subscribes to UserDraft's reactive cell when a live entry exists. None did for the preview path, so the chat's raw-app writes (UserDraft.save / setDraftAndMeta, from write_app_file / patch_app_file / write_app_runnable) only touched localStorage and the open preview never updated. Hold a live handle via UserDraft.useMany (reactive getter so it re-acquires when open_preview swaps the path without remounting) and read inbound through handle.draft. Verified in-browser: an external UserDraft.save live-updates the bound summary in the open preview.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): make flow preview reactive to AI draft writes
Mirror of the script/raw-app preview fixes, completing two-way binding for all three session editor kinds. FlowEditorView read the draft via static UserDraft.get inside an effect, which only subscribes to UserDraft's reactive cell when a live entry exists — none did, so the chat's writes (write_flow / patch_flow_json / set_flow_module_code) only touched localStorage and the open preview never updated. Hold a live handle via UserDraft.useMany (reactive getter so it re-acquires when open_preview swaps the path without remounting) and read inbound through handle.draft. Verified in-browser both directions: an external UserDraft.save live-updates the flow header summary and rebuilds the module graph; a preview edit propagates through the debounced save to both UserDraft.get and the chat's getGlobalDraft adapter.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(sessions): surface local-storage drafts in fork diff & compare page
Augments the backend fork-vs-parent comparison with browser-local (UserDraft) drafts so a session's uncommitted AI/user changes are visible in the Fork Diff Viewer and the /forks/compare page. Adds forkDraftDiff.ts (augmentForkComparisonWithLocalDrafts + getForkItemValue), a 'local changes detected' / new-draft warning surface (checkbox-slot warning icon, no-op-baseline filtering, dedup), a 'Local draft <> fork' tab in DiffDrawer, and selectTooltip/nonSelectableTooltip plumbing in Row/WorkspaceDeployLayout.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Revert "feat(sessions): surface local-storage drafts in fork diff & compare page"
This reverts commit 3cfd858e36.
* fix(sessions): leave for home when switching workspace from the session page
An AI session is scoped to its (forked) workspace, so it makes no sense to keep showing it after the user picks a different workspace. The workspace switcher's link href now points home on the session route (the link navigation wins over onClick's preventDefault), and toggleSwitchWorkspace also redirects home there as a fallback. Session-switching uses a separate path (syncWorkspaceTo), so it's unaffected — which is why reacting at the switcher is more robust than watching workspaceStore.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): clear session highlight off the session page; default delete-fork on
Two SessionPicker fixes: (1) only highlight the active session while on the /sessions route — currentSessionId lingers after navigating away, so the row stayed selected in the sidebar; gate the highlight on the route. (2) The 'Also delete forked workspace' toggle in the delete-session modal now defaults to on (the fork is tied to the session and would be orphaned otherwise); resets keep it defaulted-on for the next open. User can still untick it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(copilot): say "local storage" instead of "draft" in write-tool status
The global chat's write tools persist to the browser's localStorage (UserDraft), not a workspace draft. The tool status / result messages now say the item was saved to local storage (and discard says it was discarded from local storage) so users aren't misled into thinking a workspace draft was created. Covers the shared script/flow/trigger/resource/variable helpers and the app tools.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): sidebar collapse, new-session chat, fork delete & not-found nits
- Hide the collapse chevron and make the section header non-interactive when there are no sessions; reset the persisted collapsed state while the list is empty so the first session always appears expanded.
- Stop grafting a recent past chat onto a freshly created session: ensureChatIdsSeeded now skips transient sessions, so the seed only pairs untagged chats with pre-existing sessions.
- After deleting a fork from a session (SessionPicker / SessionWrapper), fall back to the fork's parent workspace when the deleted fork was the active one, instead of stranding the user on a deleted workspace.
- Show a 'Session not found' message (with a New session action) when the URL names a session that doesn't exist, rather than rendering a blank page.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(copilot): expose preview tools only to session chats
open_preview and get_preview_status drive a session's side-panel editor, so they only make sense inside an AI session. They were always present in the global tool list and just errored when called outside a session. Now AIChatManager carries an isSessionChat flag (set by sessionRuntime.createRuntime); the GLOBAL-mode branch uses globalToolsFor({ sessionPreview }) to drop the two tools for the regular side-panel chat, and prepareGlobalSystemMessage omits their guidance unless previewTools is set. The module-level handlers + in-tool error guards stay as defense in depth.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(flow-editor): move intra-editor chat preservation to its own PR
The beforeNavigate / preserveChatOnDestroy guard that keeps the global FLOW
chat alive across same-flow editor remounts is a standalone global-chat fix,
unrelated to sessions. Split out to #9339; FlowEditor reverts to the plain
session-guarded saveAndClear lifecycle here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(raw-app): pre-boot session editor hidden so files open instantly
In single-view (sessions) the UI Builder iframe was mounted inside a
display:none wrapper while the Preview tab was active, so the VS Code
workbench booted at 0x0, threw in its LayoutService ("Unable to figure
out browser width and height"), and wedged the editor on "Loading
editor" with no recovery when later revealed.
Keep the iframe mounted at the editor area's real width and hide it with
visibility instead of collapsing it: Monaco boots correctly while hidden,
and revealing a file is an instant un-hide (no reload, no relayout, no
latency).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(flow-ai): move flow-group color-palette work to its own PR
The flow-group color-palette guidance + validateFlowGroups guard + tests are
an independent flow-AI improvement, not part of sessions. Split out to #9343;
these three flow files revert to their main state here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): hide the in-editor Flow AI Chat button in the session preview
The flow preview pane in a session already sits next to the session's own AI
chat, so FlowBuilder's in-editor "Flow AI Chat" toggle (which opens the global
singleton chat) is redundant and confusing there. Pass
customUi={{ topBar: { aiBuilder: false } }} from FlowEditorView, reusing the
existing showFlowAiButton gate (!disableAi && customUi?.topBar?.aiBuilder !=
false) that flows down to FlowStickyNode — no new prop needed.
Verified in-browser: the button (WandSparkles) renders in the regular
/flows/edit route but is absent in the session preview for the same flow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): mirror /scripts/add for never-saved scripts in editor preview
An AI-created script with no backend version yet left savedScript undefined
in the session preview, which disabled Save draft and hid Show diff. Open it
as a new script (empty initialPath) like /scripts/add so Save draft is enabled
and creates it on first save; seed the path as already-chosen
(initialPathChosen) so the summary->path auto-slug does not rename the
AI-assigned path. On first save ScriptBuilder writes savedScript back through
the bind and flips into edit mode (Save draft + Show diff) without navigating
away.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sessions): refresh fork diff count after an editor draft save
The fork-bar diff count reads a cached comparison refreshed only on AI-turn-end or tab refocus. A 'Save draft' in the session editor registers in the backend fork tally asynchronously (~300ms after the create returns), so the count stayed stale until one of those triggers fired. Add SessionRuntime.scheduleForkComparisonRefresh() (re-fetches at 700ms + 2200ms to clear the async tally) and wire it to onSaveDraft in ScriptEditorView and FlowEditorView.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sessions): don't auto-open the settings drawer in script preview
When the AI's open_preview tool previews a never-saved script, ScriptEditorView
passes initialPath='' so ScriptBuilder behaves like /scripts/add. That empty
path also triggered ScriptBuilder's auto-open of the settings drawer, which is
unwanted in the session preview where the AI manages metadata. Pass
neverShowMeta so the drawer stays closed on mount; the Settings button still
opens it manually.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sessions): don't host legacy drag-and-drop apps in the editor preview
The session preview pane only hosts code-based items (flow, script, raw
app). Drop the legacy 'app' kind from SessionTarget and the open_preview
tool, and route a legacy app picked in the drill picker to the standalone
/apps/edit editor instead. Removes the now-dead AppEditorView and its
runtime load path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sessions): don't prompt to discard raw-app changes on navigation
In a session the raw-app editor's content is continuously persisted to the
UserDraft (localStorage), so tearing the editor down on navigation loses
nothing. Skip the UnsavedConfirmationModal (and its beforeNavigate guard)
when the editor is mounted inside a session pane; the standalone /apps_raw
editor still shows it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sidebar): pin Help to the bottom instead of floating
The bottom of the sidebar stacked the User/Settings cluster and the Help
block with a fixed ~40px gap between them, plus a bottom margin that kept
Help from sitting flush — so Help appeared to float. Drop those fixed
margins so the cluster and Help stay glued at the bottom with a small gap
and Help is flush, and let mt-auto own the flexible space above the group.
Add pt-4 so the cluster keeps a minimum gap from the Triggers section when
the sidebar runs out of room and that flexible space collapses.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sessions): surface diff/discard for AI script drafts in preview, refresh diff on deploy
loadScript built the editor's scriptStore by aliasing and mutating savedScript.val, so the deployed baseline got overwritten with the draft content and the diff compared draft-vs-draft. Clone the baseline before layering the AI draft on top. On load, when the local draft diverges from the saved baseline, surface a toast ('AI saved a local draft') with Show diff (opens the diff drawer with a Discard-draft button) and Discard local draft — mirroring the regular /scripts/edit affordance the session's parallel loader omitted. Also wire onDeploy (alongside onSaveDraft) to scheduleForkComparisonRefresh so the fork diff count refreshes after a deploy, not just on an AI turn or tab refocus.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sessions): script preview restore/deploy feedback; drop on-load draft toast
- Implement real restoreDeployed/restoreDraft for the diff drawer: the shared loadScript-based handler was a no-op (loadScript early-returns on the loaded path and would re-read the local draft). Reset the live UserDraft handle to the chosen baseline (deleting the backend draft for 'restore to deployed') so the inbound effect syncs the editor.
- Show a 'Deployed' toast on deploy: the default Deploy takes ScriptBuilder's no-toast branch (the editor navigates away instead); the session stays put, so surface the success toast.
- Remove the on-load 'AI saved a local draft' toast: unnecessary in a session, where the user already expects their changes to be present. Diff/discard remain reachable via ScriptBuilder's Show diff + the diff drawer's restore buttons.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sessions): gate breadcrumb picker draft-merge behind the dev flag
The WorkspaceItemDrillPicker merges localStorage UserDrafts into its
navigable items so in-flight session/chat drafts are reachable. That
merge was ungated, so with the sessions dev flag off it also surfaced
the standalone editors' autosave drafts — they appeared as navigable
rows that 404 on the backend draft fetch. Gate aiDraftsForKind on
isGlobalAiEnabled() so it is a no-op without the flag (no sessions
exist then anyway); inside sessions the merge still works.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(editor): reload script/flow editor on client-side breadcrumb nav
Picking a different item in the editor-header breadcrumb picker calls
goto() for a client-side navigation. SvelteKit reuses the same +page
instance across a path-param change, but the script and flow editor
routes captured `draftPath` and the `UserDraft.use()` handle once at
mount and never remounted ScriptBuilder/FlowBuilder. The URL and title
updated while the editor kept showing the previous item's breadcrumb,
summary and content; only a full reload showed the navigated-to item.
Mirror the pattern the app / raw-app editors already use:
- Derive the draft path from the URL and key the handle off it via
`UserDraft.useMany` (a stable proxy onto the current handle), so the
reload reads/writes the navigated-to item's draft instead of the
previous one's — fixing the stale draft-comparison too.
- Gate the builder subtree on a `renderEditor` flag flipped false when a
navigation kicks off the reload and true once the data is ready, so
the builder cleanly unmounts and remounts once against stable data. A
synchronous `{#key}` swap instead races Monaco's async init against
the torn-down container.
- Flows also reset `nobackenddraft` per navigation so a fresh load
reconsiders the backend draft.
The unsaved-changes guard is unaffected (it runs in beforeNavigate,
before the remount). The app and raw-app editors already handled this
and are unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(sessions): sync preview with the deployed version on editor + chat deploy
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(sessions): reload the preview after a chat raw-app deploy
The deploy-reload-preview callback added previously only fired for script and
flow. Now that the merged deploy_workspace_item tool can deploy raw apps
(bundle + createAppRaw/updateAppRaw), wire raw apps in too. A raw app deploys
under type 'app' but the session preview addresses it as 'raw_app', so the
deploy handler maps 'app' -> 'raw_app'; the runtime open-check gains the
loadedRawAppPath case. syncPreviewWithDeployed already handled 'raw_app'
(discard the local draft + force-reload via loadRawApp), so no runtime change
was needed there.
Adds a unit test asserting deploy_workspace_item(type:'app') notifies the
session handler with { kind: 'raw_app', path }.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): address Claude PR review (3 P1 + 3 P2 + test)
P1:
- Drop the hardcoded placeholder default sessions (u/guilhempw/...). New users
(empty/cleared/private-browsing localStorage) now start with no sessions and
see the empty state instead of unresolvable "session not found" rows.
- Scope the preview/deploy tool handlers to the *calling* session. open_preview,
get_preview_status and the deploy reload handler dispatched via the global
currentSessionId, so a backgrounded session's tool call mutated the UI-active
session. The calling session id is now carried in the per-manager tool
`helpers` (AIChatManager.sessionId, set in createRuntime) and threaded through
the tool ctx to the handlers, which dispatch to it (falling back to the active
id only when absent). Keeps backgrounded sessions isolated.
- beforeSend now aborts the send on failure: commitSessionWorkspace throwing used
to be swallowed, letting the message go out against the wrong workspace
silently. Now it toasts and returns. Also guarded the unguarded
listUserWorkspaces refresh in materializeFork's duplicate-key self-heal so a
second network failure can't rethrow past the toast-and-return contract.
P2:
- disposeRuntime now clears the fork-comparison refresh timers (700ms/2200ms)
via a new runtime.dispose(), so an evicted/deleted runtime can't fire a stray
refreshForkComparisonNow/compareWorkspaces after teardown.
- Convert Svelte 4 on:click -> Svelte 5 onclick on the Button components in
SessionWrapper, SessionForkBar, ForkDiffDrawer, SessionPicker, sessions/+page.
- WorkspaceItemRow's <a href> branch gains role="option" + aria-selected to match
the <button> branch, for consistent listbox semantics.
Tests:
- core.test.ts: deploy_workspace_item(type:'app') threads the calling session id
through helpers to the deploy handler ({ sessionId, kind:'raw_app', path }).
- New sessionState.test.ts unit-tests deriveForkStatus + isForkSession across
all branches (root/fork/unavailable/draft, ahead/behind/diverged/in_sync).
svelte-check 0 errors; 57 frontend unit tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(copilot): collapse deploy preview-reload dispatch to a type→kind map
Replace the if/else-if that mapped deploy type to preview kind with a single Partial<Record<WorkspaceItemType, ...>> lookup + one if. Non-previewable types map to undefined → no dispatch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(copilot): use getAiChatManager() instead of inlining the context fallback
Six chat components still inlined
`getContext<AIChatManager>('aiChatManager') ?? singletonAiChatManager` even
though aiChatManagerContext.ts already exports getAiChatManager() for exactly
this (the resolve-scoped-instance-or-fall-back-to-singleton pattern, already
used by AIChatDisplay/AIChatInput/AIChatMessage/CodeDisplay). Adopt it in
DatatableCreationPolicy, ChatMode, ToolExecutionDisplay, AIChat,
AskUserQuestionDisplay and flow/FlowAIChat, and drop the now-unused getContext /
AIChatManager / singletonAiChatManager imports (FlowAIChat keeps getContext for
its FlowEditorContext/FlowCopilotContext lookups).
No behavior change — getAiChatManager() is the same resolution.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): consistent script deploy → preview sync; trim session deploy menu
Two related session deploy fixes + clarifying comments.
1. Hide the extra deploy-dropdown options in the session script preview. The
editor always "stays" and is already scoped to a fork, so Deploy & Stay here,
Fork, Edit in workspace fork, Exit & See details and Export as YAML/JSON make
no sense there — only "Show diff" is kept. ScriptBuilder gains
`inSessionPane = !!getContext('aiChatManager')` (same pattern ScriptEditor
uses) and gates those items. (They were correctly absent for never-deployed
session scripts but leaked for deployed ones.)
2. Fire onDeploy on every successful script deploy. ScriptBuilder previously
skipped onDeploy for "Deploy & Stay here" and lib scripts (it just re-pinned
parent_hash + toasted), so a session preview wouldn't sync after those. Now
onDeploy always fires with a `stay` flag; route consumers skip navigation when
stay (behaviour identical to before — stay → toast only, primary → navigate),
and the session ignores stay and always syncs. With (1) hiding Deploy & Stay,
this now covers the lib-script-in-session case.
3. Comments: RawAppEditorHeader / AppEditorHeader note that the
`if (!inSessionPane) UserDraft.remove` guards are intentional — the editor
doesn't own the localStorage draft in a session (the runtime does, keyed by
the fork); the session-side equivalent is the View's onDeploy →
runtime.syncPreviewWithDeployed (discard fork draft + reload to deployed).
svelte-check 0 errors; session dropdown verified to show only "Show diff" for a
deployed script, route deploy menu unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sidebar): single Menubar so bottom menus hover-switch (WIN-1993)
The bottom sidebar group split Settings/Workers/Folders/Logs and Help across
two separate <Menubar> components. melt-ui's hover-to-switch (open menu closes
when another trigger in the same Menubar is hovered) only coordinates within a
single Menubar, so hovering between the two groups left both menus open
(stacked) instead of switching. Collapse them into one Menubar, wrapping each
group in its own flex container to preserve spacing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(editor): gate external code sync behind opt-in syncExternalCode prop
The unconditional `code` prop->Monaco sync effect added for sessions
live-preview ran for every <Editor> caller (14 call sites). Most either
bind:code with their own external-sync (e.g. ScriptEditor) or treat code as
init-only, so a blanket setValue risked clobbering them. Gate the effect on a
new opt-in `syncExternalCode` prop (default off) and enable it only at the two
flow inline-rawscript editors — the case that actually needs external updates
(AI chat editing a flow module's content reflecting live in the preview).
Verified in-browser: AI-driven external edit to a flow step now reflects live
in Monaco, and typing keeps the caret intact (round-trip guard).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sessions): address P1 review findings (commit-abort, render-stuck, workspace sync)
From the cubic/Claude PR review:
1. beforeSend now aborts the send when the workspace isn't committed. The earlier
fix only caught a *thrown* error, but commitSessionWorkspace returns undefined
(never throws) when a staged fork fails to materialise — so the first message
+ its tool calls shipped to get(workspaceStore) (the parent). beforeSend now
throws on undefined so AIChatManager's catch toasts + aborts.
2. The script/flow edit reload effect set renderEditor=false then called
loadScript()/loadFlow(); a rejected fetch left renderEditor stuck false, so the
editor pane vanished and never remounted. Both calls now .catch → toast +
renderEditor=true (token-safe), so the pane always remounts.
3. SessionWrapper.moveAndActivate now syncWorkspaceTo(target) — moving a session
off an unavailable workspace was leaving the app pointed at the old one
(mismatch with moveSessionToNewFork / handleConfirmedDelete).
Test: sessionState.test.ts pins commitSessionWorkspace's failure contract
(returns undefined + drops pending_fork when the fork fails) — the invariant the
beforeSend abort relies on. svelte-check 0 errors; 58 frontend unit tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): address P2 review findings (cubic)
Draft round-trip:
- appDraftCodec: carry custom_path through runtimeRawAppToDraft /
applyDraftToRuntimeRawApp (+ seed it in loadRawApp) so a session round-trip
no longer erases a raw-app draft's custom URL.
- sessionRuntime.loadScript "no draft" path: structuredClone the baseline before
setting parent_hash — it could alias `result` (= savedScript.val) and corrupt
the pristine deployed baseline the diff drawer reads.
- FlowEditorView: include `summary` in the inbound/outbound dedup sigs so
summary-only changes propagate/persist.
Workspace-state on navigation:
- SidebarContent (post-delete) and workspace_settings (post-archive): guard the
listUserWorkspaces() refresh so a transient failure can't strand the user on
the just-removed workspace, and refresh the list before switching to parent.
- WorkspaceMenu: keep ?workspace=<id> in the session-page workspace href so a
modifier/middle click (which bypasses onClick) lands in the right workspace.
UI/keyboard:
- WorkspaceItemRow: indent adds to the px-3 base (calc) instead of replacing it.
- ForkDiffDrawer: ArrowLeft maps a 2-segment file path (f/foo) to its scope
folder (folder:f/foo) instead of a nonexistent folder:f.
- flows/edit: defer flowBuilder setup (primary schedule, draft triggers,
loadFlowState) until after the builder remounts (renderEditor=true + tick),
so reload-time state restoration isn't skipped on the unmounted builder.
svelte-check 0 errors; 58 frontend unit tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(sessions): unit-test the P1/P2 review fixes (extract pure helpers)
Extract the pure logic touched by the review fixes into small tested helpers
(behaviour-preserving) and add unit tests:
- appDraftCodec.test.ts — custom_path survives the runtime↔draft round-trip (A1).
- forkDiffNav.ts/.test.ts — parentFolderKey (extracted from ForkDiffDrawer):
ArrowLeft parent resolution incl. the 2-segment-path case (C2).
- workspaceMenuHref.ts/.test.ts — extracted from WorkspaceMenu: session-route
href keeps ?workspace=<id>; off-session swaps the param (B2).
- flowDraftSig.ts/.test.ts — extracted from FlowEditorView (dedups 3 sig sites):
the dedup signature includes summary, so summary-only changes propagate (A3).
(commitSessionWorkspace failure-contract test for the beforeSend P1 landed with
the P1 commit.) svelte-check 0 errors; 75 frontend unit tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): address second-round review (Pi + Codex)
Three findings flagged post-push (cubic was fully addressed in the prior
commits; this commit covers the new ones):
- [P1] commitSessionWorkspace non-fork branch — when a session created
inside a fork defaults pending_workspace_id to the family root, commit
set s.workspace_id but never synced workspaceStore. First send's
logAiChat + tool calls then ran against the wrong (still-fork)
workspace. Fix: syncWorkspaceTo(ws) after the commit, mirroring the
pending_fork branch's switchWorkspace(newId).
- [P1] Warm-session live-editor slot hijack — /sessions keeps up to 3
warm-mounted sessions; UserDraft stores one live editor per
(workspace, kind). Each editor view unconditionally claimed the slot,
so a hidden warm session in the same workspace+kind could overwrite
the visible session's claim — chat actions like discard /
"the open editor" then resolved to the wrong session. Fix: thread
isActiveSession from SessionWrapper into Script/Flow/RawAppEditorView
and gate setLiveEditorDraft on it.
- [P2] ForkDiffDrawer stale per-item raw diff cache — loadedDiffs /
summaries persist for the drawer's lifetime; fetchComparison refetched
on each open() but loadDiffFor short-circuited on cached keys, so an
edit-then-reopen showed fresh counts but stale expanded content. Fix:
clear both records at the top of fetchComparison.
Tests:
- sessionState.test.ts: 2 tests pinning commitSessionWorkspace's
workspaceStore sync (mismatch and matching).
- userDraft.test.ts: 3 tests pinning the live-editor slot collision
(regression), the active-session gate, and cleanup ordering.
- forkDiffCache.test.ts (new): 2 tests for the drawer cache
invalidation contract via fetchComparison simulation.
Verified end-to-end in browser: P2 (close+reopen drawer triggered an
identical second batch of per-item get fetches), P1#1 (new-session send
from a fork synced localStorage.workspace to root and posted chat to
/api/w/local/...), P1#2 (raw_app slot for workspace=local correctly
follows the visible session across A→B→A switches while both stay
warm-mounted). svelte-check 0 errors; touched test suites green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(api): handle multi-version scripts when removing granular ACL
remove_granular_acl built a CTE whose `old` subquery selects one row per
matching table row, then read it back via the scalar subquery
`(SELECT old_write FROM old)` in the RETURNING clause. For the `script`
table the PK is (workspace_id, hash), so a path with multiple deployed
versions yields several rows sharing the same (workspace_id, path). When
two or more versions carried the ACL key, `old` returned multiple rows and
PostgreSQL rejected the scalar subquery with "more than one row returned by
a subquery used as an expression", making it impossible to remove an ACL
entry from a script's permissions panel.
All versions share the same extra_perms value (the UPDATE applies to every
matching row), so any single row's old_write is representative. Add
`LIMIT 1` to the scalar subquery. Other kinds are unaffected because they
have a unique constraint on (workspace_id, path/name).
Introduced by b3603d872 (#7365). Add a regression test reproducing the
multi-version case at the SQL level.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: remove granular ACL regression test
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: trim ACL fix comment
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cache): allow overriding hub base url via env in `cache` mode
The `windmill cache hubPaths.json` prebuild step (run in the Dockerfile) never
connects to the DB, so HUB_BASE_URL stays at its compiled default
(https://hub.windmill.dev) — unlike server/worker modes which load it from the
DB global setting. This made it impossible to point the prebuild cache step at
a private or staging hub.
Read HUB_BASE_URL from the environment at the start of cache_hub_scripts and
store it into the existing HUB_BASE_URL ArcSwap (the same static the hub fetch
functions read). No effect unless the env var is set and non-empty; server and
worker modes are unchanged (they still use the DB setting).
This also enables validating hub-script dependency changes end-to-end against a
local fake-hub before pushing to the real hub.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(security): re-pin cached hub scripts to patched versions
windmill-integrations#133 was merged and pushed to the hub, minting new
versions with regenerated (CVE-free) lockfiles. Bump the hubPaths.json pins so
the prebuild cache step (`windmill cache`) fetches the patched lockfiles instead
of the old vulnerable ones (the hub serves each version_id immutably, so the old
pins keep returning the vulnerable deps until bumped).
- slackErrorHandler 19741 -> 28241
- slackRecoveryHandler 9080 -> 28239
- slackSuccessHandler 28220 -> 28240
- smtpReport 9086 -> 28242
- appReport 28076 -> 28243 (puppeteer screenshot script)
- gitInitRepo 28219 -> 28229 (already-fixed hub version; pin was stale)
Validated end-to-end against the real hub: `windmill cache` with these pins
produces a clean cache_nomount/bun (axios 1.16.1, form-data 4.0.5,
follow-redirects 1.16.0, nodemailer 8.0.10, ws 8.21.0, svelte 5.55.8,
devalue 5.8.1; basic-ftp and ip-address no longer pulled). No vulnerable
versions remain.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Harden slim image security surface
* Limit slim hardening to libgnutls pin
* fix(docker): pin libgnutls30 to exact +deb12u7 in slim images
Replace the +deb12u* wildcard with the exact current security build so
builds fail loudly when Debian ships a newer patch, prompting an
explicit review/bump rather than silently floating forward.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(docker): drop libgnutls30 version pin, keep explicit listing
The version pin (exact or wildcard) was not the load-bearing part of
this change. Naming libgnutls30 explicitly in apt-get install is what
forces apt to upgrade it past the base image's older pre-installed
version — transitive deps from wget/curl/git would otherwise leave it
in place because their version constraints are already satisfied.
Dropping the version specifier so each rebuild picks up the current
security build automatically, matching the PR's stated intent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(docker): drop libgnutls30 comment
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(flows): preserve step/subflow worker tags under a custom-tagged flow
A flow running on a custom worker tag force-propagates that tag to every
descendant step, script and nested sub-flow, overriding their own declared
tags. This made it impossible to route a specific step or sub-flow to a
different worker group. The new opt-in FlowValue.preserve_step_tags lets a
step that declares its own non-empty tag run on it; untagged steps still
inherit the flow tag. Defaults off to preserve existing behavior.
* chore: regenerate system prompts for preserve_step_tags
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(flows): nest preserve_step_tags toggle under flow worker tag setting
The toggle only affects routing when the flow has a custom worker tag, so
show it as a sub-setting of the Worker Group tag picker, visible only once a
tag is set, instead of as a standalone option.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(flows): allow step worker tag picker when preserve_step_tags is enabled
When a flow defines a worker tag, the per-step tag picker was replaced by a
read-only "Flow's WG" label. With preserve_step_tags enabled the step's own
tag is honored, so the picker must remain editable in that case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(flows): propagate preserve_step_tags to branch and loop bodies
payload_from_modules built the synthetic RawFlow for branch/loop bodies with
a default FlowValue, dropping preserve_step_tags. Tagged steps inside a
branch or loop therefore still inherited the parent flow tag even with the
flag enabled. Thread the flag through to the synthetic FlowValue so the
behavior is consistent for nested containers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(flows): clear preserve_step_tags when flow worker tag is removed
Avoids the flag lingering as invisible state after the flow tag (and its
toggle) are removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(flows): repair preserve_step_tags propagation to branch/loop bodies
The previous commit added flow.preserve_step_tags at the payload_from_modules
call sites but the parameter and FlowValue field were not actually threaded
through (a failed edit left the function unchanged), so the crate did not
compile. This completes the change: payload_from_modules takes preserve_step_tags
and sets it on the synthetic FlowValue for branch/loop bodies.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(flows): complete preserve_step_tags propagation to branch/loop bodies
Previous two commits left windmill-worker uncompilable: payload_from_modules
received flow.preserve_step_tags at its call sites but the parameter and the
synthetic FlowValue field were not actually added. This adds the parameter,
sets preserve_step_tags on the synthetic FlowValue, and threads
flow.preserve_step_tags through all five call sites.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(flows): clear preserve_step_tags whenever the flow worker tag is removed
The flag was only reset when the Worker Group toggle was switched off, not
when the tag was cleared directly in the picker (or via the YAML editor),
leaving preserve_step_tags=true as invisible state with the advanced badge
still reporting it active. Move the cleanup into the reactive block that
already tracks the flow tag so every clear path is covered.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* docs(skills): document mandatory //native marker for bunnative and nativets
* docs(skills): clarify windmill-client is the only allowed library in native mode
* docs(skills): remove legacy nativets skill in favor of bunnative
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(skills): fix bunnative import rule - any bundleable lib, not just windmill-client
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): make public apps opt into cross-origin isolation via wm_coep
Public app pages served at /public/* and custom paths /a/* were not
getting the COEP/COOP/CORP headers, so they were blocked when embedded
as an iframe inside a cross-origin-isolated page (e.g. another raw app,
which sets Cross-Origin-Embedder-Policy: require-corp). A nested
document loaded into a require-corp context must itself set COEP for
the iframe to load.
Rather than applying the isolation headers to all public pages (which
would also force COEP on classic apps and break subresources without
CORP, e.g. external image URLs or embeds), public apps now opt in via
a `wm_coep` query param on the embed URL:
<iframe src="https://<domain>/public/<ws>/<secret>?wm_coep=on">
The app publish drawer gains a URL/Embed toggle: "URL" shows the plain
shareable link (param-free), "Embed" shows a ready-to-copy iframe
snippet with wm_coep baked in, so the flag is discoverable exactly when
embedding and absent otherwise.
`wm_coep` is consumed internally and stripped from the app `query`
context so it doesn't collide with app-defined params. Only params we
own are stripped (an explicit set), not the whole `wm_` prefix.
Fixes GIT-884
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* nit
* nit
* fix(apps): only bake wm_coep into embed snippet for raw apps
AppEditorHeaderDeploy is shared by the classic (AppEditorHeader) and raw
(RawAppEditorHeader) deploy drawers. The embed snippet unconditionally
appended ?wm_coep=on, which for a classic/low-code app forces COEP
require-corp on the document and breaks no-CORP cross-origin subresources
(external <img> in AppImage/AppStatCard/AppNavbar, {@html} embeds in
AppHtml, CDN import() in AppCustomComponent) — the exact regression the
opt-in design avoids.
Add a `rawApp` prop (default false); the raw header passes rawApp. The
flag is appended only for raw apps; classic apps get a plain iframe
snippet, and the wm_coep helper text is shown only for raw apps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
CLI deploys (sync push, set-permissioned-as) went through the same
create/update endpoints as a UI "deploy from draft", which delete the
draft at that path. That silently wiped teammates' in-progress drafts on
every push. Add a transient skip_draft_deletion deploy flag (mirroring
deployment_message) that the CLI sets; the backend then skips the
DELETE FROM draft for scripts, flows, and apps. UI deploys are unchanged.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
get_token_using_oauth resolved the AI OAuth resource's token_url and
POSTed to it without any SSRF validation, while base_url is validated in
get_base_url. A workspace member with resources:write could point
token_url at an internal/metadata address (e.g. 169.254.169.254),
turning the server into an authenticated blind SSRF probe.
Validate the resolved token_url with validate_url_for_ssrf before the
request, gated behind the same ALLOW_PRIVATE_AI_BASE_URLS opt-in as
base_url so private AI deployments keep working consistently for both
URL fields. ALLOW_PRIVATE_AI_BASE_URLS is now pub so windmill-api can
reuse it instead of re-parsing the env var.
* fix: trigger git sync for re-encrypted secrets on encryption key change
When changing a workspace encryption key, the secret variables get
re-encrypted with the new key, but the git sync was only dispatched for
the encryption_key.yaml metadata file. Repos with Secrets sync enabled
were left with stale ciphertexts until the next per-variable deployment.
Now, after the transaction commits, we also dispatch a Variable git sync
event for each re-encrypted secret so the new encrypted values are
pushed to the configured repos. Errors are logged but don't roll back
the key rotation.
Fixes WIN-1994
Fixes#9344
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: batch encryption-key rotation into one git-sync job
Workspace encryption key rotation now re-encrypts every secret variable
and then dispatches a single batched git-sync job carrying the Key event
plus one Variable item per re-encrypted secret. Repos with Secrets sync
enabled receive every new ciphertext in one commit instead of nothing
(previously only `encryption_key.yaml` was pushed) — and instead of N
separate jobs the debouncer might or might not merge.
Wires through the new `handle_deployment_metadata_batch` entry point
added in the companion EE PR; OSS has a no-op shim so the build stays
green.
Adds an integration test (`workspace_encryption_key_git_sync`) asserting
that rotating the key with 3 secret variables in scope produces exactly
one deployment-callback job whose `items` array contains the Key event
+ all 3 variable entries and `skip_secret=false`.
Fixes WIN-1994
Fixes#9344
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref for git-sync helper simplification
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: cover non-debouncing git-sync fallback on key rotation
Adds a regression test exercising a workspace whose sync script predates
hub version 28103: the rotation must still queue a legacy-format
deployment-callback job per item (encryption_key + each re-encrypted
secret) instead of silently skipping the repo. Bumps ee-repo-ref to the
EE fallback fix.
Addresses the P1 raised in the PR review (Codex/Pi/Claude): batch path
dropped git sync entirely for repos without sync-job debouncing support.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: add sqlx offline cache for encryption-key git-sync test queries
The cargo_test CI job builds with SQLX_OFFLINE=true; the two new
sqlx::query!/query_as! calls in
windmill-api-integration-tests/tests/workspace_encryption_key_git_sync.rs
had no cached entries, failing the build with E0282. Regenerated and
added only the two new query caches (no EE/feature cache loss).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref to updated EE companion PR (08e3b9b)
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(ai): disable redirect following on AI proxy client to close SSRF
The AI proxy validates the configured base_url against SSRF rules but the
shared HTTP client followed up to 10 redirects without revalidating the
hops, so a public base_url could 3xx the server into a private/internal
address (e.g. the Docker socket or cloud metadata). Disable redirect
following so the validated host is the only one the server connects to.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(ai): remove heavy redirect SSRF integration test
Drop the integration-test-level regression for redirect following; it
spins up a full API server + DB for a one-line client-config change.
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(api): don't follow symlinks when reading service log files
Defense in depth on top of the existing `..` path-traversal check in
the get_log_file handler: reject the request if the final path
component is a symlink, so a planted symlink in the logs directory
cannot be used to read arbitrary files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(api): authorize and harden the jobs_u get_log_file endpoint
The unauthenticated jobs_u get_log_file endpoint served any job's log
file to anyone who knew the job UUID, with no authorization. Gate it the
same way as get_job_logs: look up the job (the log directory name is the
job id) filtered by workspace and the caller's scope tags, and only allow
non-logged-in callers to read logs of jobs created by the anonymous user.
Also add defense in depth: refuse to read through a symlink so a planted
symlink in the logs directory cannot be used to exfiltrate arbitrary files.
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 fork-branch deployment callback runs the hub script
sync-script-to-git-repo-windmill, which imports `windmill-cli` as a pinned
npm dependency and runs it in-process (it does NOT shell out to a PATH wmill).
hub/28236 pinned windmill-cli@1.706.1, whose `git-deploy --only-create-branch`
path returns early without pushing — so the fork branch was checked out
locally but never published to the remote. #9366 fixed the CLI and shipped it
as windmill-cli@1.712.0, but without a hub-script bump the running callback
still used 1.706.1.
Bump LATEST_GIT_SYNC_SCRIPT_PATH to hub/28238, which is identical to 28236
except it pins windmill-cli@1.712.0 (content + lockfile). This fixes
test_workspace_fork_creates_branch and production fork-branch creation.
Also add backend/windmill-common/src/workspaces.rs to the git-sync-test
path-gate so future script-path bumps trigger the e2e (the bump alone is not
otherwise covered by the gate).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(oauth): support per-provider sandbox URLs in registry + instance settings
* fix(oauth): polish sandbox review nits (cc lookup, header label, ee ref)
* refactor(oauth): drop dead build_oauth_clients duplicate in windmill-oauth
* refactor(oauth): derive sandbox-capable provider list from registry
* chore(docker): copy oauth_connect.json into frontend build stage
* test(oauth): cover sandbox helpers (as_sandbox, canonical_name, resolve)
* chore: update ee-repo-ref to 9297d8f790346e6a6ad540c7bca1a67f91ec11a2
This commit updates the EE repository reference after PR #595 was merged in windmill-ee-private.
Previous ee-repo-ref: 3ab3eca9ac15ebab6db991e7964bc5e48ce21f42
New ee-repo-ref: 9297d8f790346e6a6ad540c7bca1a67f91ec11a2
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* [ee] fix(git-sync): publish fork branch on only_create_branch from the CLI
Fixes WIN-1997. Forking a git-sync-configured workspace must push a
`wm-fork/<branch>/<id>` branch to the repo, but the integration test
`test_workspace_fork_creates_branch` failed: the fork callback job
succeeded yet no branch appeared.
Root cause: the fork-branch callback runs the sync script with
`only_create_branch: true` and no items. The hub sync script delegates
branch checkout to `wmill sync git-deploy --only-create-branch` and runs
its own in-process commit+push ONLY for the `!only_create_branch` path
(`if (!only_create_branch) git_push(...)`). #9284 had moved commit+push
out of the CLI to the caller for the GPG-cache-warmth invariant
(WIN-1974) — but it also dropped the CLI's push for the branch-only
case. A branch-only publish has no commit, so no signing is involved and
the GPG concern does not apply; with neither the CLI nor the hub script
pushing, the empty fork branch was never published.
Restore the CLI push for the `only_create_branch` path (a bare
`git push --porcelain` of the checked-out branch ref). Adds a
deterministic CLI regression test that runs `git-deploy
--only-create-branch` for a fork workspace and asserts the branch
reaches the remote with no caller-side push.
EE companion: format the fork-branch commit message with Display instead
of Debug (no more `Some("...")` leak).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to a30079e75dc5b7d7413aa8ee20e40e80bfea9cbd
This commit updates the EE repository reference after PR #597 was merged in windmill-ee-private.
Previous ee-repo-ref: 8b02336fcebdfae4b9d2795cbb74fa7046530bcb
New ee-repo-ref: a30079e75dc5b7d7413aa8ee20e40e80bfea9cbd
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>
* [ee] feat(queue): duration-weighted fairness admission atomic
Add the `WORKSPACE_FAIRNESS_ADMISSION_PPM` atomic that the EE
`workspace_fairness_ee::refresh_overloaded` writes on each refresh
(see companion EE PR). The atomic is read on every pull by
`should_admit_capped` to decide whether the dispatch goes down the
standard or fairness path. Defaults to 10_000 (= admit all) so the
pre-fairness behaviour is preserved until the first refresh fires.
OSS stub in `workspace_fairness.rs` continues to return `true`
unconditionally, so non-EE builds are bit-identical.
* docs(queue): consolidate full fairness algorithm into workspace_fairness.rs
Move the algorithm doc — what "overloaded" means in worker-seconds, the
duration-weighted admission derivation, coordinated refresh structure,
audit emission, the SQL perf constraints (no params CTE, drive running
side from v2_job_runtime), and EE gating — into the OSS surface module
where it is readable without EE access. The EE file becomes implementation
only.
Also bump ee-repo-ref to the EE commit that strips the duplicate doc.
* docs(queue): clarify ADMISSION_PPM default is "admit all", not count-based
Addresses CI review (claude[bot]): the `10_000` initial value is the
"admit all" no-op default that applies before the first refresh
classifies an overloaded set — not the count-based value (which would
be `target * 10_000`). The count-based form is the empty-bucket fallback
inside `compute_admission_ppm`, a different thing.
* chore(queue): point ee-repo-ref at EE main (fairness admission merged via #593)
* fix(queue): duration-weighted admission uses unclamped service-time window
Bumps ee-repo-ref to the EE fix (windmill-ee-private#596) that sources
D_c/D_u for the admission probability from a separate 60s service-time
window of true `duration_ms`, instead of the occupancy aggregation whose
per-job contributions are clamped to the 10s occupancy window. The clamp
truncated D_c for capped jobs longer than the window, under-admitting the
duration skew (true 34s jobs → ~86% effective share instead of the target
65%). Occupancy worker-seconds still drive overload classification.
Updates the algorithm doc in workspace_fairness.rs accordingly.
Note: ee-repo-ref points at the EE feature branch; re-point to EE main
once #596 merges.
MultiSelect read `value.length` directly while `value` is a bindable
prop with no default, so a parent passing `undefined` (e.g. an
enum-array approval form field with no initial value via ArgInput)
threw a TypeError that blanked the entire approval page. Guard all
reads behind a `value ?? []` derived.
Fixes WIN-1996
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
referencesIncludeLine required the include token to be the entire
trimmed line. The wmill-default CLAUDE.md template is
`Instructions are in @AGENTS.md` — include mid-sentence — so the
migration prompt fired every run on files wmill itself wrote.
Accept the include as a whitespace-separated token on any non-comment
line.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Help menu lived in a separate Menubar from the Settings/Workers/
Folders/Logs group, so melt-ui's hover-to-switch logic (which only
spans menus within the same Menubar) did not close the Help popup
when the cursor moved to a sibling group, causing menus to stack.
Merge both bottom Menubars into a single Menubar, wrapping each
group in its own flex container to preserve the visual spacing.
Fixes WIN-1993
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): redact encryption_key diff in stdout by default
Sync diff output previously printed the full encryption_key contents on
stdout whenever the workspace key changed locally or on the remote, which
made it easy to leak the key via shell history, CI logs, etc. Now the
diff is replaced with a redacted notice for any encryption_key change in
both prettyChanges and showConflict. Pass --show-encryption-key-diff
(also configurable via wmill.yaml's showEncryptionKeyDiff) to opt back
into the full diff.
Fixes WIN-1992
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): redact encryption_key diff with fixed-length mask
Drop the --show-encryption-key-diff opt-in and always redact: the diff
now keeps the first 5 chars of the key so rotations are still visible
(different prefixes), then replaces every remaining char with `*` so the
length of the key is preserved without leaking it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`announce_server_started` writes a `server_heartbeat:{INSTANCE_NAME}` row
on each startup. INSTANCE_NAME is a fresh random string per process, so
the row is never updated again and a new row is inserted on every
restart, growing background_task_state unboundedly.
Add an hourly monitor task that deletes server_heartbeat:* rows older
than 7 days. Older rows cannot influence check_any_server_started (which
only considers heartbeats refreshed after the restart was initiated), so
they are safe to prune.
Fixes WIN-1990.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(queue): bump EE ref to include worker_ping fairness signal
The current ee-repo-ref.txt pointed to 31cda7c (an unrelated merge
commit on the asset-graph-view-ee branch) instead of ddc9e80, which
contains the workspace-fairness fix that switches the active-share
signal from v2_job_queue.running=true to worker_ping. As a result
cloud was still computing overload off the legacy signal, so a
workspace with many in-flight/suspended flows (lancom01-prod, with
799 suspended flows × 3 v2_job_queue bookkeeping rows each = 2397
running-true rows) was flagged as 95% of cluster activity despite
consuming zero worker slots.
Bumping to ddc9e80 picks up the worker_ping-based signal, which
naturally excludes (a) suspended jobs (no worker pinging them),
(b) zombie running-rows from dead workers, and (c) flow/flownode
orchestration rows that never run on a worker in the first place.
* test(queue): seed v2_job rows + realistic durations for fairness helpers
The new duration-weighted fairness algorithm joins v2_job_queue and
v2_job_completed to v2_job for the `kind` filter (excluding flow
bookkeeping) and reads `duration_ms` for the completed contribution.
Update the test helpers to mirror that schema:
* `insert_completed` now inserts a matching v2_job row (kind=script)
and writes `duration_ms = 1000` with a 1-second [started_at,
completed_at] interval, so each completed row contributes ~1
worker-second when fully inside the refresh window.
* `insert_queued` likewise pre-inserts v2_job, sets `started_at`
to NOW() - 1s when running=true (so running rows contribute ~1
worker-second by the time the refresh runs), and seeds
v2_job_runtime.ping so the running side accrues real-time worker
seconds (the algorithm bounds end-of-interval by ping).
The zombie/suspended insert helpers are intentionally left without
v2_job rows — the new algorithm's INNER JOIN excludes them, so they
still correctly contribute zero worker-seconds.
* chore(queue): bump EE ref to duration-weighted fairness algorithm
Companion to windmill-ee-private#<TBD>: switch the EE workspace
fairness aggregation from a count-based UNION (worker_ping snapshot
+ v2_job_completed count) to a worker-seconds aggregation sourced
directly from v2_job_queue and v2_job_completed, with kind/suspend
filters mirroring handle_zombie_jobs and per-row defenses against
zombie inflation on both halves.
* chore(queue): bump EE ref for fairness perf fix (inline window_start)
* chore(queue): bump EE ref for fairness perf rewrite (driver-side flip)
* update ee ref
`fairness_ignores_zombie_running_rows` and
`fairness_ignores_concurrency_suspended_rows` panic intermittently in CI
(both Linux and Windows runs). Mark them `#[ignore]` until the
underlying flakiness is resolved.
* feat(websocket-trigger): honor HTTPS_PROXY/HTTP_PROXY/NO_PROXY (WIN-1988)
`tokio_tungstenite::connect_async` opens a raw TCP socket and ignores
the standard outbound-proxy env vars, so deployments behind a forward
HTTP proxy can't reach the WebSocket endpoint and Test Connection
times out after 30s.
Add a small `proxy` module that resolves the right proxy URL for the
target host (HTTPS_PROXY for wss://, HTTP_PROXY for ws://, NO_PROXY
exclusions, ALL_PROXY fallback, lowercase variants), opens an HTTP
CONNECT tunnel when one applies, and hands the resulting TcpStream to
`client_async_tls_with_config` for the TLS + WS handshake. Direct
connect remains the default when no proxy env is set.
Unit tests cover NO_PROXY matching, proxy URL parsing (including IPv6
literals and basic-auth userinfo), and the CONNECT handshake itself
against an in-process fake proxy (success, basic-auth header, 407
rejection).
Fixes WIN-1988
* refactor(websocket-trigger): reduce blast radius and reuse existing logic
Follow-up to the proxy support change. Three things:
1. Skip the new code path entirely when no proxy is configured.
`connect_async_with_proxy` now checks the env-var snapshots up front
and delegates straight to `tokio_tungstenite::connect_async` if
neither `HTTP_PROXY` nor `HTTPS_PROXY` is set. Same fall-through
applies when proxy env is set but `NO_PROXY` excludes the host or
the proxy URL doesn't parse. Non-proxied deployments now exercise
exactly the previous code path.
2. Move the `NO_PROXY` / `HTTP_PROXY` / `HTTPS_PROXY` env-var snapshots
from `windmill-worker::worker` into `windmill-common`. The worker's
`PROXY_ENVS` static now reads from there, and the websocket trigger
reads from the same source — one place reads the env, one source
of truth for both call sites.
3. Replace the hand-rolled proxy-URL parser with `url::Url::parse`
(already a workspace dep, used across the codebase). Half the LoC
and handles edge cases (userinfo percent-encoding, IPv6 literals,
path/query stripping) via the well-tested crate instead of by hand.
All 13 proxy unit tests still pass. `cargo check` is clean.
* fix(websocket-trigger): unbreak EE build + trim proxy tests
- Re-export `NO_PROXY` / `HTTP_PROXY` / `HTTPS_PROXY` from
`windmill-worker::worker` (via `pub use windmill_common::...`) so the
EE `otel_tracing_proxy_ee` module's `use crate::{HTTPS_PROXY, ...}`
resolves like it did before. Fixes the `check_ee_full` / `cargo_test`
CI failures from the previous commit.
- Trim the proxy tests to one un-ignored canary
(`http_connect_tunnel_sends_well_formed_request_and_unwraps_stream`)
that exercises the actual on-wire CONNECT handshake plus byte-perfect
tunnel passthrough. The NO_PROXY-matching, URL-parsing, and edge-case
tunnel tests are kept under `#[ignore]` for manual debugging
(`cargo test -- --ignored`) since they're either delegated to
`url::Url::parse` or trivial string matching — low ROI on every CI run.
* feat(raw_apps): surface UI Builder build errors over the preview pane
Companion to the matching change in the UI Builder repo (see linked PR),
which stops rendering the build-error overlay over the VS Code editor
iframe and instead emits a `buildError` postMessage on every build
(message: undefined on success to clear).
Listen for that message on the existing window message handler (already
source-gated by the UI Builder iframe), store it in a `buildError`
$state, and surface it in two places:
* A red banner over the preview iframe, sibling to the existing logs
overlay (`top-12 left-2 right-2 z-20` so it clears the tab bar) —
failures appear right where the user looks for the rendered output.
* The Preview tab's icon and label tint red
(`text-red-600 dark:text-red-400`, matching the existing error
convention in raw_apps) — important in single-tab mode where the
preview pane is collapsed to 0px and the banner would be hidden.
Done by mapping `leftPaneTabs` / `rightPaneTabs` through a small
`tintPreviewOnError` helper so the source-of-truth `tabs` array is
untouched (DnD, ordering, fallback selection keep using the original
previewTab object).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(raw_apps): use Alert component for the build-error banner
Replace the hand-rolled red div with the shared `Alert` component
(`type="error"`, `title="Build failed"`). The error text stays in a
`<pre>` child so multi-line bundler output keeps its formatting, with
`max-h-60` so a long error never takes over the whole preview pane.
The absolute-positioned wrapper (`top-12 left-2 right-2 z-20`) and the
`role="alert"` move to that wrapper so the Alert component itself stays
unstyled at the call site.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(raw_apps): solid bg-surface backing behind build-error Alert
The Alert's error background is semi-transparent in dark mode
(`bg-red-900/40` in `common/alert/model.ts`), so the preview iframe
shows through when the banner is laid over it. Add a `::before`
pseudo on the Alert root with `bg-surface` (matched `rounded-md`,
`-z-10` so it sits behind the red bg) to give it a solid plate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(raw_apps): isolate banner stacking context, DRY tab tint chain
Two small follow-ups from review:
* Add `isolate` to the build-error banner wrapper so the `before:-z-10`
pseudo's stacking context is pinned locally — it works today because
`position: absolute` + `z-20` creates one, but `isolate` makes the
dependency self-documenting and survives a future refactor that
removes the explicit `z-20`.
* Extract `tintTabs = (ts) => ts.map(tintPreviewOnError)` so the two
`$derived` blocks for leftPaneTabs / rightPaneTabs read identically.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(raw_apps): trim build-error overlay comments
Per review feedback. Keep only the load-bearing facts (bg-surface backs
the Alert's translucent red, isolate pins the pseudo stacking, the
`message: undefined` clear convention) and drop the prose context that
duplicated what the code already shows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(raw_apps): bump bundled ui_builder to 00c9834
Brings in the postMessage emission from
windmill-labs/windmill-code-ui-builder#9 (merged) so this PR's host
listener actually receives `buildError` events. SHA verified against
the R2 artifact.
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(jobs): enforce anonymous-only guard on `only_result` job updates
The `jobs_u/getupdate/{id}` and `jobs_u/getupdate_sse/{id}` endpoints
accept `only_result=true`. In that branch, `get_job_update_data` queried
the result solely by (workspace_id, job_id) and skipped the
`created_by == "anonymous"` check that the non-only_result path and
adjacent unauthenticated endpoints apply. An unauthenticated requester
who learned a private job UUID could therefore retrieve that job's
output.
Hoist the guard to the top of `get_job_update_data` so both branches are
covered.
Fixes WIN-1980
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: fold `created_by` check into existing only_result queries
Avoids the extra `SELECT created_by` round-trip per call by joining
`v2_job` once in the two queries that handled the unauth path and
checking inline. Behavior is identical to the prior commit; the SSE
polling loop now does one query per poll instead of two for
unauthenticated callers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: cache anonymous_verified across SSE polls
Replace the LEFT JOIN approach with an upfront `SELECT created_by`
guarded by a new `&mut bool anonymous_verified` parameter that mirrors
`early_return_suppressed`. The SSE polling loop now performs the auth
check exactly once per stream rather than per poll, and the data SQL
reverts to its original form so authenticated callers pay no extra
cost. `created_by` cannot change after job creation, so caching the
verification across polls is safe.
Cost matrix:
- Authed (any path): 0 extra queries
- Unauthed one-shot: 1 extra query (unavoidable)
- Unauthed SSE: 1 extra query at stream start, 0 per poll
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: scope anonymous check to only_result branch
The non-only_result branch already enforces the `created_by` check via
its main query, so a top-level hoisted check duplicated work for
unauthenticated default-path callers. Move the check inside the
`if only_result.unwrap_or(false)` block — exactly where the bypass
lives — and leave the non-only_result path untouched.
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] feat(service-accounts): allow choosing role at creation time
Previously, service accounts were hardcoded to operator and could not be
used as the CLI sync user since they had no write access. They also only
counted as 0.5 seat each.
This change:
- Extends `NewServiceAccount` to accept optional `is_admin` / `operator`
(defaults to `operator=true` for backward compatibility).
- Exposes a role picker in `AddUser.svelte` when creating a service
account (Operator / Developer / Admin).
- Lets admins update a service account's role from the user list (it
used to be locked to "Operator" with a tooltip).
- Updates the OpenAPI spec + regenerates the frontend client.
A developer/admin service account counts as 1 seat under the existing
seat-cap logic (operators stay at 0.5).
Companion PR on windmill-ee-private updates the `INSERT INTO usr` to
honour the chosen role.
Fixes WIN-1985
* [ee] feat(service-accounts): wm_deployers opt-in for Dev role
When creating a service account with role=Developer, surface a toggle
"Add to wm_deployers" (recommended). Members of wm_deployers can deploy
on behalf of other users — the typical setup when the service account is
used as the CLI sync / CI deploy identity.
- `NewServiceAccount` gains an optional `add_to_deployers` flag.
- Frontend defaults the toggle to on but only shows it under Developer
(admins have it implicitly; operators can't deploy).
- Tooltip links to docs.windmill.dev "Run on behalf of".
Companion EE PR updates the handler to INSERT into usr_to_group for
wm_deployers when the flag is set.
Refs WIN-1985
* chore: update ee-repo-ref to 974ed42067d9f63acb42332b671b8c01ffd4b625
This commit updates the EE repository reference after PR #589 was merged in windmill-ee-private.
Previous ee-repo-ref: f7dbc3cc2ba21c396f4828881e3b9d9ab6f50c69
New ee-repo-ref: 974ed42067d9f63acb42332b671b8c01ffd4b625
Automated by sync-ee-ref workflow.
* [ee] fix(service-accounts): unhardcode role in superadmin user list
Two review issues from the merged #9307 / #589:
1. P1 — The global Users tab in #superadmin-settings still pinned every
service account to "Operator". Now it shows the actual role
(Admin / Operator / Developer), derived from the SA's usr row.
- `list_users_as_super_admin`: replaced `true as operator_only` with
the real `operator` value, and added `is_workspace_admin` from the
row (NULL for password users since their admin status is
per-workspace).
- `global_whoami`: when the email belongs to a service account, look
up its real `operator` / `is_admin` instead of pinning to operator.
- `SuperadminSettingsInner.svelte`: drop the hardcoded "Operator"
badge; render Admin / Operator / Developer using the new fields,
matching the workspace-level view.
2. P2 — Regenerate the bundled `openapi-deref.{yaml,json}` so the
`createServiceAccount` body (now exposing `is_admin`, `operator`,
`add_to_deployers`) and the new `GlobalUserInfo.is_workspace_admin`
field show up at runtime in `/api/openapi.{yaml,json}`.
Bumps `ee-repo-ref.txt` to the EE follow-up that adds the offline
seat-cap check on `create_service_account`.
Refs WIN-1985
* chore: update ee-repo-ref to b7a6068c1f3dc845e012959268b2426f0de4d697
This commit updates the EE repository reference after PR #590 was merged in windmill-ee-private.
Previous ee-repo-ref: 0b1307c21d1bfd6fb43a03c2ba39d2a8bf8e6470
New ee-repo-ref: b7a6068c1f3dc845e012959268b2426f0de4d697
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(settings): skip workspaced-route duplicate checks on cloud
The pre-write validation hooks for `app_workspaced_route` and
`http_route_workspaced_route` query the DB for cross-workspace duplicates
and fail the save when any are found. On cloud both `custom_path_exists`
(apps) and `route_path_key_exists` (HTTP triggers) already scope lookups
by `workspace_id` regardless of these settings, so duplicates across
workspaces are expected and the validation has no runtime meaning. The
result was that any cloud super-admin attempting to save instance
settings with these toggles set to false received
`Duplicate HTTP route paths detected` even though the setting has no
effect on cloud routing.
Fixes WIN-1983
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(error): render JsonErr as readable text and return 400
`Error::JsonErr` previously rendered through `#[error("Error: {0:#?}")]`,
leaking Rust's `Debug` output (`Object { "error": String(...), "details":
Array [...] }`) into the HTTP response body, and was bucketed into the
catch-all 500 branch in `IntoResponse`. The result was a 500 status with
a wall of Rust debug syntax in the toast — confusing and user-hostile.
- Bucket `JsonErr` into 400 (Bad Request): every current call site
(workspaced-route duplicate checks, OAuth client errors, etc.) is a
client/validation issue, not an internal server fault.
- Add `format_json_err_message` which surfaces the `error` field as the
headline, summarises `details` (with a `- key=value` per entry), and
pretty-prints the rest as JSON for unknown shapes. The frontend toast
now reads e.g.
Duplicate HTTP route paths detected
- route_path=a, workspace_id=admins, http_method=post
- route_path=a, workspace_id=starter, http_method=post
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(toast): preserve newlines and escape HTML in multi-line errors
The toast renders via `{@html processMessage(message)}`, so server-side
error bodies that span multiple lines (e.g. the duplicate-route response
from the settings endpoint) collapsed into a single line because HTML
treats consecutive whitespace (including `\n`) as a single space.
When the message contains a newline, escape HTML first (defends against
injected markup in server error bodies) and convert `\n` to `<br />` so
multi-line errors stay readable in the toast.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fixup: address CI review feedback
- toast.ts: escape HTML unconditionally. The previous gate on `\n` left
single-line server error bodies unsafe under {@html}, which cubic
flagged as P0. The path regex below only inserts a `<span>` around a
`u/...` or `f/...` capture that can't contain HTML metacharacters, so
escaping the whole input is the simpler and correct fix.
- error.rs: add unit tests pinning the rendered shape of
`format_json_err_message` (error+details, error-only, truncation cap,
non-object fallback to pretty JSON).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(raw_apps): custom tab system for source / runnable / preview
Replaces the fixed split-pane layout with a tab bar inside the editor
area. Each frontend file is a tab, each selected runnable is a tab,
and the Preview is pinned to the right (non-closable). Tabs are an
alternative discoverability surface to the sidebar — both stay
functional, but tabs make navigation viable on small screens with
the sidebar collapsed.
A "Split with Preview" toggle in the tab bar's trailing slot pairs
the active tab with the preview side-by-side for wide-screen
multitasking. The toggle hides when Preview is already the active
tab.
The UI Builder, runnable editor, and preview iframe all stay mounted
across tab switches (toggled via `display`) — no bundler restarts, no
preview state loss, no editor remounts.
- New common/tabs/DraggableTabs.svelte: reusable tab strip with
drag-reorder (@windmill-labs/svelte-dnd-action), pinned-left/right
slots excluded from the drag zone, hover-revealed X close, middle-
click close, keyboard navigation (arrows / Enter / Backspace),
and a `trailing` snippet for inline toolbar add-ons.
- raw_apps/RawAppEditor.svelte:
- Tab state (`tabs`, `activeTabId`, `splitWithPreview`) lives in
Windmill. Persisted in localStorage keyed by workspace + app path.
- Sidebar file clicks (`handleSelectFile`) and runnable selection
(`selectedRunnable` via `bind:`) are mirrored into tabs via an
effect — the sidebar interaction is otherwise untouched.
- Listener augmented: `setActiveDocument` backfills tabs for files
VS Code opens by itself; `setFiles` / `runnables` updates drop
stale tabs.
- Bundler / inspector / rebuild toolbar moves into the tab bar's
trailing slot — always visible regardless of active tab.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(raw_apps): modern tab styling + resizable split-with-preview
Two polish passes on the new tab system:
DraggableTabs styling:
- Remove the bottom border on the tab strip + the accent-coloured
border-b-2 on the active tab. The active tab now shares the
surface background with the content area below it, so the
boundary visually "disappears" — modern IDE-style tabs.
- Inactive tabs sit on the darker surface-secondary tab strip and
get a subtle right separator so they don't blur into each other.
Split-with-Preview is now a real resizable Splitpanes:
- The content area is rendered as a Splitpanes (always), with the
source/runnable slot on the left and the preview iframe on the
right. The user can drag the divider to adjust the ratio when
the "Split with Preview" toggle is on.
- Iframes never remount across single↔split toggles — pane sizes
are driven reactively from (activeTabKind, splitWithPreview),
not by adding/removing the Splitpanes itself.
- The user's preferred split ratio is remembered while they're
dragging and reapplied next time split is enabled.
- The inner splitter is CSS-hidden in single mode so the toggle
button stays the single canonical way to flip layouts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(raw_apps): split mode moves preview tab into the right pane
Cleaner mental model for split-with-preview. Instead of "split the
active tab + always keep the Preview tab around", the Split toggle
now physically moves the Preview tab out of the bar and into a
permanent right pane. When the user toggles split off, the Preview
tab reappears in the bar like any other tab.
- New `displayedTabs` derived: filters out the Preview tab when
splitWithPreview is on, so the user sees only file/runnable tabs
in the bar and a dedicated preview pane on the right.
- `toggleSplit` redirects the active tab to the most recent
file/runnable when the user toggles split on with Preview active,
so they don't end up staring at an empty left pane.
- Split toggle is now always visible — the user can flip both ways.
The button label flips between "Pin preview to the right" and
"Move preview back into a tab" to reflect what's about to happen.
- reorderTabs preserves the Preview tab in the underlying `tabs`
array even though it's filtered out of the drag set in split mode.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(raw_apps): VS Code-style "Preview" header on the right pane
In split mode, the right pane now shows a small "Preview" tab-styled
header anchored at its top-left — making the layout read like a real
VS Code editor split, where each group has its own tab bar.
- Header appears only when `splitWithPreview && activeTabKind !== 'preview'`
(i.e. when the right pane is meaningfully separate from the left's
content). In single mode with preview active, the right pane is the
only thing visible and the main tab bar already labels it.
- The header uses the same styling as an active tab: `bg-surface`
on a `bg-surface-secondary` strip, h-8, text-xs, no border.
- An X button next to the label toggles split off — equivalent to
closing the editor in VS Code's split view (preview goes back to
living as a tab in the main bar).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(raw_apps): VS Code-style symmetric tab bars per pane
Restructure the editor area so each pane is a self-contained "editor
group" with its own tab bar at the top. The Splitpanes is now the
topmost element — the divider runs floor-to-ceiling, splitting both
the tab bars and the content.
Layout (left pane = source / runnable, right pane = preview):
- Left pane top: DraggableTabs (file/runnable tabs, Preview tab when
split is off) + Split-toggle in the trailing slot.
- Right pane top: a custom preview header — "Preview" label styled
like an active tab on the left + the preview-affecting toolbar
(bundler, inspector, rebuild) on the right.
- Each pane independently sized via Splitpanes; iframes + the
runnable panel stay mounted and toggled via `display` so state
survives every transition.
Trade-off: in single-mode with Preview active (paneA=0), the left
tab bar is hidden along with the left pane. To switch back to a
file tab the user uses the sidebar — which is exactly the
discoverability surface tabs were meant to complement, not replace.
Button placement by semantic ownership:
- Layout control (Split toggle) — left side, with the editor.
- Preview-affecting controls (bundler, inspector, rebuild) — right
side, with the preview. No close-X on the right; the Split toggle
on the left is the canonical way to flip layouts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(raw_apps): keep tab bar visible when Preview is active in single mode
The "VS Code-style" restructure put the tab bar inside the left
Pane. When activeTabKind became 'preview' in single mode, the left
pane collapsed to width 0 and the entire tab bar disappeared with
it — leaving the user with no way to switch back to a file tab
except via the sidebar.
Move the main tab bar back above the inner Splitpanes (full width,
always visible). The preview pseudo-header stays inside the right
pane, carrying the bundler / inspector / rebuild toolbar. The
splitter only goes through the content area below the tab bar,
which is acceptable given how much friction the disappearing-tabs
edge case caused.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(raw_apps): per-pane tab bars with mirrored single-mode lists
Replace the single tab bar above the inner Splitpanes with one
DraggableTabs per pane. Splitter now goes floor-to-ceiling through
tabs AND content in split mode.
In single mode both bars mirror the full tab list, so the visible
pane always carries every tab — fixes the bug where activating
Preview hid the tab strip. Clicking Preview while in split mode is
a no-op (Preview is permanently visible in the right pane).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(raw_apps): polish tab strip and sync editor font to text-xs
* feat(raw_apps): move logs overlay onto the preview pane
* refactor(splitpanes): extract pixel-aware minSize helper
* fix(raw_apps): tab hydration loads correct file; closeTab in split mode
* fix(raw_apps): lazy-mount UI Builder iframe + add dev:ui-builder script
* feat(raw_apps): default split view, blue preview tab, fix dnd ghosting
* fix(raw_apps): remove 1px splitter sliver beside preview in single view
* fix(raw_apps): tab scrollbar on hover, fix thumb height + resize staleness
* refactor(raw_apps): don't persist tab/split layout in localStorage
* refactor(raw_apps): derive pane sizes + binding setter instead of effects
* style(raw_apps): trim verbose comments
* feat(raw_apps): accept appendLogs delta from the UI Builder iframe
* fix(raw_apps): exit inspect mode on Escape
* fix(raw_apps): Escape clears lingering inspector selection after pick
* style(raw_apps): accent-selected styling for active tab, bg-surface strip
* fix(raw_apps): address PR review nits (drop debug log, timer/reorder/pane-setter, dev script restore)
* fix(raw_apps): clear inspector overlay on the preview iframe, not the source
* style(raw_apps): neutral tab look (surface-tertiary/text-emphasis selected, text-hint idle)
* chore(raw_apps): bump bundled ui_builder to 61b6fdd
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ai-chat): align footer bar, use DropdownV2 for mode/autonomy selectors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(dropdown): add `selected` item prop rendering a trailing check
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style(ai-chat): add small spacing between chat input and footer bar
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ai-chat): always offer the 3 autonomy options in the auto-accept picker
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai-chat): default autonomy mode to auto-accept on
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(ai-chat): use Button component for footer dropdown triggers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style(ai-chat): use a hand icon for the auto-accept-off autonomy state
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style(ai-chat): use subtle Button variant for mode and model selectors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style(ai-chat): tighten spacing between input and footer bar
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ai-chat): reword autonomy levels as ask/auto-accept/bypass permissions
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(button): add 2xs unified size with tighter padding
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ai-chat): compact footer bar — 2xs buttons, AtSign context icon, short Yolo label, discreet model
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style(ai-chat): widen the permission selector dropdown
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(dropdown): group shortcut + selected check to avoid ml-auto collision
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(ai-chat): cover getPersistedAutonomyMode default; clarify default comment
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(queue): audit-log workspace-fairness cap transitions
When the cloud per-workspace fairness mechanism adds a workspace to the
capped set or releases one, write `workspace_fairness.capped` /
`workspace_fairness.uncapped` audit-log entries to the affected workspace.
The cluster admin can review the full timeline from the `admins` workspace
audit view with `all_workspaces=true`; per-workspace owners see their own
events in their normal audit list.
Only the per-cycle refresh winner emits entries (matching where the heavy
aggregation runs), so a fleet of N workers does not produce N duplicates
per transition. The diff is computed against the value already in
`background_task_state` rather than the winner's in-memory cache, so a
freshly-restarted process winning the claim does not spuriously emit
"newly capped" entries for workspaces that were already capped before it
started.
Audit writes are best-effort: failures are logged via tracing and do not
abort the refresh cycle.
Fixes WIN-1984
* feat(queue): scope fairness audit to admins workspace + queue-metrics pane
- Write `workspace_fairness.capped` / `workspace_fairness.uncapped` to the
`admins` workspace (was: per-affected-workspace) with the affected
workspace_id moved to the `resource` field. Cluster admins now get the
full timeline in one place without `all_workspaces=true`.
- Add `GET /workers/workspace_fairness_events` returning the last 100
events. Cloud-gated (returns `[]` on non-cloud) and devops-only.
- Add a `WorkspaceFairnessEvents` Section to the Queue Metrics drawer,
rendered only when `isCloudHosted()` is true. Shows time / event
badge / workspace / parameters with a refresh button.
Fixes WIN-1984
A token scoped to a single resource (e.g. `resources:read:u/alice/foo`)
could call `GET /api/w/{w}/resources/list_search` and receive `path` and
`value` for unrelated resources in the workspace. Route-level scope
checks only validate `domain:action`; per-resource handlers do a
`check_scopes` against the path, but the listing endpoints did not —
leaking integration credentials, API keys, and other secrets stored as
resource values to narrowly-scoped tokens.
Add `build_scope_path_predicate` to `windmill-api-auth` (mirrors
`check_scopes` semantics but parses the token's scopes once, suitable
for filtering many rows). Apply it to `list_search_resources`,
`list_resources`, `list_names` (resources) and `list_variables`
(non-secret value leak), so a scope-restricted token only ever sees the
paths it is authorized to read. Unscoped tokens and tokens whose only
scopes are `if_jobs:filter_tags:*` are unaffected.
Includes regression tests covering: unscoped, tag-filter-only,
single-resource, wildcard, wrong-domain, and write-implies-read.
Fixes WIN-1981
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(queue): cloud-only per-workspace fairness cap on the shared worker pool
On `app.windmill.dev` the cluster runs a single default worker group, so a
single workspace flooding the queue can degrade quality of service for
everyone else. This adds an opt-in mechanism that caps any single workspace
at a configurable share of the shared worker pool when it has been
dominating cluster activity for more than a configurable window.
Detection signal counts both currently-running jobs and jobs completed in
the rolling window, so it catches workspaces hogging slots with long jobs
**and** workspaces spamming many tiny jobs (where no individual job's
started_at is old, but throughput share dominates).
Refresh is coordinated cluster-wide via a single UPDATE on
`background_task_state`: the `WHERE updated_at < now() - interval` predicate
combined with row-level locking means only one process per refresh cycle
actually runs the aggregation, regardless of fleet size. Every other
process gets the freshly written value in the same round trip via
`UNION ALL ... LIMIT 1`. Heavy aggregation rate stays at ~0.2-0.5 qps for
the whole cluster.
Pull queries are split: the existing query string and its bind shape stay
bit-identical to today, so the planner keeps using the same indexes when
fairness is off or no workspace is currently capped. A separate
`WORKER_PULL_QUERIES_FAIRNESS` adds `AND workspace_id <> ALL($2::text[])`
and is only materialized while the feature is enabled.
Hard-gated to `CLOUD_HOSTED=true` + BASE_URL host == app.windmill.dev at
three layers: frontend `cloudonly: true`, API setter rejection in
`set_global_setting_internal`, runtime check in `fairness_active`. Settings
are exposed under Jobs in the instance-settings UI; defaults are off so
the change is a no-op for self-hosted.
Two-pass pull guarantees no worker idling: if every queued job belongs to
a capped workspace, the second pass uses the unmodified pull queries.
Cap re-asserts on the next refresh.
Fixes WIN-1982
* fix(queue): address CI review findings on workspace fairness
Six fixes from the four-reviewer cross-check on #9303:
1. **Aggregation evaluation (Codex P1).** The previous `INSERT ... ON CONFLICT
DO UPDATE WHERE updated_at < ...` had the heavy `v2_job_queue ∪
v2_job_completed` aggregation inlined into `VALUES`, which Postgres
evaluates for every contender to build the proposed row — losing the
"one heavy aggregation per cycle cluster-wide" property the design
advertises. Split into three small statements: (a) cheap claim with
constant `VALUES`, (b) winner-only `UPDATE ... SET value = jsonb_build_object('overloaded', <agg>)`
(Postgres only evaluates `SET` per row matching `WHERE`, so losers never
compute the aggregation), (c) read for everyone. Heavy query now truly
runs ~0.2-0.5 qps cluster-wide regardless of fleet size.
2. **Numeric setting wraparound (cubic P1).** `u64 as u32` and downstream
`u32 as i32` could silently flip sign and feed `make_interval(secs => -N)`,
making `now() - interval` a future timestamp and disabling the
completed-jobs half of the activity signal. Clamp `duration_secs` to
[1, 86400] and `min_total_jobs` to [0, u32::MAX] before storing.
3. **`/instance_config` bypass (cubic/Claude/Codex P2).** Bulk config endpoint
sidestepped `set_global_setting_internal`'s gate; a self-hosted superadmin
could persist `workspace_fairness_*` rows via the bulk path. Mirror the
per-key check in `set_instance_config` upsert flow.
4. **DB error coerced to false (Claude P2).** `load_workspace_fairness_enabled`
collapsed `Err(_)` to `false` and unconditionally swapped the atomic — a
transient DB blip during notify-event propagation toggled the feature off
cluster-wide (and triggered a `store_pull_query` rebuild precisely when load
is highest). Now propagates the error so the atomic stays at its prior value.
5. **Refresh failure cooldown (Claude P2).** Storing `0` removed the rate
limit entirely; every subsequent pull spawned a new refresh task. Leave
`LAST_REFRESH_MICROS` at `now_us` (already written by the CAS) so the
natural interval acts as the cooldown.
6. **Visibility + duplication (Pi P2).** Mark `make_pull_query_fairness` as
`pub(crate)`. Move the duplicated `BASE_URL host == app.windmill.dev`
parser into `windmill-common::worker::is_cloud_production_host` and share
it between the API setter and the runtime path.
Verified locally:
- `POST /api/settings/global/workspace_fairness_enabled` → 400 (per-key gate)
- `PUT /api/settings/instance_config` with fairness key → 400 (bulk gate)
- `cargo check --workspace --features=private,enterprise,quickjs` — clean
Refs WIN-1982.
* fix(queue): second round of CI review nits on workspace fairness
Three issues raised by the Codex/Claude re-review of commit 0b38ff2:
1. Non-cloud deletes were rejected (Codex P2). The cloud gate ran before
the Null / empty-string deletion branches in both `set_global_setting_internal`
and the bulk `set_instance_config`. A self-hosted instance that inherited
stale `workspace_fairness_*` rows from a cloned cloud DB couldn't clear
them through the API — the rows stayed in `global_settings` and continued
to show up in the YAML export. Now the gate only blocks upserts; Null /
empty-string deletes pass through on any host.
2. Deleted numeric knobs kept stale runtime values (Codex P2). When a
cloud admin cleared `workspace_fairness_max_percent`, `..._duration_secs`,
or `..._min_total_jobs`, the notify-event fired but the numeric loaders
ignored `Ok(None)` and left the previous in-memory value pinned until
process restart. Loaders now distinguish three outcomes:
- `Err(_)`: transient — leave atomic alone (preserves the
previous-round fix).
- `Ok(None)` / `Ok(Some(invalid))`: reset to the documented default.
- `Ok(Some(valid))`: clamp and store.
Defaults are extracted to `WORKSPACE_FAIRNESS_*_DEFAULT` constants kept
in sync with the `AtomicU32::new(...)` initialisers in
`windmill-common/src/worker.rs`.
3. `fairness_active` was `pub` with no cross-crate caller (Claude nit).
Tightened to module-private.
Verified locally on this non-cloud instance:
POST .../workspace_fairness_enabled body=null → 200 (delete passes)
POST .../workspace_fairness_enabled body=true → 400 (set blocked)
PUT .../instance_config {} → 200 (no-op passes)
PUT .../instance_config with fairness key → 400 (bulk set blocked)
Skipped the partial index on `v2_job_queue WHERE running = true` that
Claude flagged as a residual nit — queue stays under 50k rows per the
operator's measurement, so the seq-scan cost (~10 ms × 0.5 qps =
~0.5% of a DB core) is well below the noise floor and the index isn't
worth the maintenance cost on job transitions.
Refs WIN-1982.
* feat(github-app): hide cloud-only UI on self-managed + admin assignment UI
Two related UX fixes for the GitHub App self-managed (GHES) integration:
1. On self-managed instances, the per-installation Export button and the
"Import installation from other instance" section in the workspace UI both
hide. Both round-trip a JWT carrying only {installation_id, account_id} with
no github_base_url, so they would produce broken cloud-style installs on a
self-managed instance. The previous Export attempt also failed with
"No JWT token received from server" because self-managed installs store an
empty JWT by design.
2. New "Workspace assignments" panel in instance settings (GhesAppSettings.svelte)
that auto-discovers installations of the configured GHES App and lets the
super-admin assign them to specific workspaces. Workspace users without
GitHub permissions no longer need to install the App themselves — the admin
provisions the link from instance settings. Admin-provisioned installs show a
"Provisioned by admin" badge in the workspace UI and can only be removed by
the super-admin from instance settings.
Backend support is in the EE companion PR
windmill-labs/windmill-ee-private#588.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to da5189cf69a453de3855057f41be0d84e5910707
This commit updates the EE repository reference after PR #588 was merged in windmill-ee-private.
Previous ee-repo-ref: d959b83ce413ad531e9cc28e0f8199cdecb73a31
New ee-repo-ref: da5189cf69a453de3855057f41be0d84e5910707
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>
* feat(typescript-client): add deleteS3File + optional workspace arg on S3 helpers
Customer-requested ergonomics for the TypeScript SDK:
- New `deleteS3File(s3object, workspace?)` wrapper around the existing
`HelpersService.deleteS3File` (backend endpoint is already there). Saves
callers from having to either hand-roll `denoS3LightClientSettings()` +
AWS SDK calls, or wire up `HelpersService` directly.
- `denoS3LightClientSettings`, `loadS3File`, `loadS3FileStream`, `writeS3File`,
and the new `deleteS3File` all gain an optional trailing `workspace?: string`
parameter that falls back to the `WM_WORKSPACE` env var via `getWorkspace()`.
Mirrors the calling convention customers already expect from helpers like
`getVariable` / `runScript`.
`build.sh` and `build.jsr.sh` are updated to export `deleteS3File` from both
the NPM and JSR entry points.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: regenerate system_prompts auto-generated for new S3 helpers
`python system_prompts/generate.py` after adding deleteS3File and the
optional workspace param to the existing S3 helpers, so the agent-facing
docs (CLI skills, TS SDK prompt, script skills) reflect the new signatures.
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(ResourceEditor): make `selected` resilient + snapshot args for React
Two issues surfaced via the React SDK (reactify wrapper re-spreads Svelte
props on every host re-render):
1. The bindable `selected` prop transiently resets to undefined on each
re-spread, flipping `current` through undefined and unmounting the
form (input loses focus on every keystroke). Rename the prop to
`selectedProp` and derive `selected = selectedProp ?? effectiveWorkspace`
so the fallback insulates the component without effects.
2. The onChange dispatch passed `current.args` (a `$state` proxy) directly,
so React consumers diffing by reference or JSON.stringify saw the same
value forever, and the effect only tracked the args reference (not
nested mutations). Wrap with `$state.snapshot` to deep-track and emit
a plain object.
The bootstrap effect is also restructured: it no longer writes `selected`
(the derived handles defaulting) and now guards on `selected in initialStates`
so workspace flips remain idempotent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ResourceEditor): declare effectiveWorkspace before use in selected
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(secret-backend): pass DB to Vault migrations + surface failure details
Companion to windmill-ee-private fix for WIN-1977. The HashiCorp Vault
migration always failed under JWT/OIDC auth because the migration
constructed VaultBackend without a DB, so every secret hit "Database
connection required for JWT authentication". Creating new secrets worked
because the runtime path passes the DB.
Frontend: when failed_count > 0, the toast and console now show the
per-secret failures (path + error, capped at 5 with "...and N more")
instead of just aggregate counts.
Fixes WIN-1977
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 14315067c083d3361512de621b12e41dbe3b017d
This commit updates the EE repository reference after PR #587 was merged in windmill-ee-private.
Previous ee-repo-ref: 390ed6c851b1915f0b492897c663f8058477680f
New ee-repo-ref: 14315067c083d3361512de621b12e41dbe3b017d
Automated by sync-ee-ref workflow.
* fix(secret-backend): escape failure fields and use <br> in migration toast
Address CI review on PR #9292:
- P1 (cubic/codex): backend-supplied workspace_id/path/error are now
HTML-escaped before being interpolated into the migration toast,
which renders through {@html processMessage(...)} in Toast.svelte.
This prevents stored XSS via secret paths or backend errors that
contain markup. '/' is intentionally left intact so the toast's
path-highlight regex still tags workspace paths.
- P2 (pi): swap '\n' for '<br>' so multi-line failure lists actually
break in the toast instead of collapsing to a single run-on line.
- Extend the same per-secret failure surfacing (toast + console.error)
to the Azure Key Vault and AWS Secrets Manager migration handlers
via a shared reportMigrationFailures() helper so all six migration
paths report identically.
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>
The bootstrap effect tracked `selected` via its early-return check, so any
time `selected` flipped back to `undefined` it would re-run and reinitialize
`states[effectiveWorkspace]` to empty — wiping user input. This happens in
the React SDK consumer: reactify re-syncs all Svelte props on every React
render, and since `selected` isn't passed through, `$props()` reverts it.
Move the `selected !== undefined` check inside the existing `untrack` so
the effect only tracks `effectiveWorkspace`. Bootstrap still runs once on
mount; subsequent `selected` flips no longer retrigger it.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(auth): reject unscoped tokens with cross-workspace forged owners (WIN-1978)
An unscoped token (workspace_id IS NULL) whose `owner` field references a
user, group, or unprefixed value that is not present in the target
workspace must not authenticate. The previous fallback in the
`u/<username>` branch granted `(is_admin=false, is_operator=true)` when
no `usr` row matched in the target workspace, letting a token holder
who could mutate the `token` table cross workspace boundaries with
operator privileges.
The `g/<groupname>` branch likewise silently accepted any group name as a
"group user", and the no-prefix branch granted operator state from
arbitrary owner strings. Both are now rejected unless the owner matches
a real user/group membership in the target workspace.
Adds an integration regression covering all three forged-owner shapes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: drop integration regression for auth fallback
The test added in the previous commit relies on a sqlx::query! that
requires offline-cache regeneration; removing per code-review preference
to keep this PR scoped to the auth-layer fix.
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 design system overhaul in 888837431c accidentally dropped the
fallback condition that displayed the Variables and Resources sections
in the prop picker by default. After that commit, these sections only
appeared when the user typed `variable.` or `resource.` in their
expression, which meant they effectively disappeared from the flow
editor's prop picker for most users.
Restore the previous behavior by showing the sections when no input
match is active (the equivalent of the old `!filterActive` clause).
Fixes WIN-1976
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: guard against null recording during FlowRecordingReplay teardown
Navigating away from a flow recording inside a workspace file-tree view
threw `TypeError: Cannot read properties of null (reading 'flow')` from
FlowGraphViewer once during the teardown tick.
Svelte 5 compiles child component props as live getters that close over
`$$props.recording.flow`. When `recording` flips to null on the parent's
navigation, an outer `{#if !recording?.flow}` doesn't stop those getters
from firing one more time as derived effects re-evaluate before the
unmount lands — so the getter dereferences null and throws.
Fix at the two layers where the deref actually happens:
- FlowRecordingReplay: use `recording?.flow` at the binding sites
(FlowViewer + graph-snippet FlowGraphViewer) so the compiler emits an
optional-chained getter, and guard the snippet branch with
`{:else if recording?.flow}` so it doesn't mount when there's nothing
to show.
- FlowGraphViewer: finish the optional chaining the rest of the file
already used everywhere else (`flow?.value?.skip_expr`,
`flow?.value?.cache_ttl`, `flow?.schema`). When the upstream
binding returns undefined during teardown, the graph degrades to an
empty frame instead of crashing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: rename package to @windmill-labs/components
- frontend/package.json: rename `windmill-components` → `@windmill-labs/components`
- frontend/publish.sh: drop the in-place sed rename dance; the checked-in name now matches what's published, so `npm run package && npm publish` is enough
- frontend/package-lock.json, system_prompts/auto-generated/prompts.d.ts: regenerated by `npm run package` under the new name
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(system_prompts): point plugin skills sync at plugins/windmill/
The plugin checkout's plugin folder is being renamed from
`plugins/windmill-code-plugin/` to `plugins/windmill/` to shorten the
slash-command namespace and align with the matching Cursor plugin
layout.
Paired with windmill-labs/windmill-claude-plugin#8. That PR must merge
first so the next sync run finds the new folder.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(system_prompts): update plugin-dir example to plugins/windmill
Co-authored-by: centdix <centdix@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: centdix <centdix@users.noreply.github.com>
* fix: use fork-scoped authed for fork visibility in compare_workspaces
* test: add EE end-to-end repro for fork rename visibility
* chore: restore concurrency_locks sqlx cache lost in cleanup
* test: add regression for stale-superadmin-token fork visibility bug
* chore: update sqlx cache for new test queries
Single contract for the deployment-callback path: the CLI does branch
checkout + pull, the caller (hub script in production, test in test)
does git add + commit + push. This restores the WIN-1974 invariant —
GPG setup and `git commit` run back-to-back in the same process, so
the agent's pre-warmed passphrase cache is still warm at sign time —
without needing a `--skip-commit` flag for the hub case and a default
"also-commit" for everything else. Same behavior in every call site.
Changes:
- sync.ts: drop the gitSyncDeployPush call from pull()'s deploy path
(both the onlyCreateBranch fast-return and the post-pull commit).
`gitSyncDeployPush` stays exported for any caller that wants the
same commit/push semantics — just not invoked by the CLI subcommand.
- gitsync_promotion.test.ts: e2e test now does its own git add +
commit + push after `wmill sync git-deploy`, mirroring what the
hub script does in production. Same regression coverage
(wm_deploy branch created in Case A, main untouched; main updated
in Case B, no new wm_deploy).
CLI typecheck unchanged (two pre-existing TarAsZip errors at lines
2578/3307, present before this PR). All 743 unit tests still pass.
The accompanying hub script (option-C — CLI for branch+pull, script
for commit+push) lives at /tmp/git-sync-diff/sync-script-to-git-repo-windmill.option-C.ts.
Once published, a follow-up bumps LATEST_GIT_SYNC_SCRIPT_PATH to its id.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(git-sync): revert LATEST_GIT_SYNC_SCRIPT_PATH to hub/28230 to restore GPG-signed deploys (WIN-1974)
hub/28231 (PR #9230) is the "thin" script that hands the actual `git commit`
to the CLI's hidden `sync git-deploy`. The hub script still does the GPG
setup (import key into a fresh GNUPGHOME, dummy `gpg -bsau` to warm the
agent passphrase cache, then `git config user.signingkey` + `commit.gpgsign`
locally), but the commit no longer runs in the same `git_push` flow — it
runs minutes later inside the CLI after workspace API resolution, zip pull,
file extraction, and lockfile autofill. By the time the spawned `git commit`
asks gpg-agent for the cached passphrase, the cache state is no longer
reliable (or the spawned `gpg` ends up talking to a fresh agent), so signing
fails non-interactively with `gpg failed to sign the data`.
hub/28230 is hub/28217's in-script logic rebuilt with windmill-cli@1.703.3:
the GPG setup and the in-script `sh_run("git commit ...")` happen back-to-back
in `git_push`, so the cache is always fresh. It preserves wm_deploy / fork
branch behavior, the EE deployment-callback `main()` signature is unchanged,
and the only min-version check in EE (`is_script_meets_min_version(28103)`)
is comfortably below 28230 — so this revert is safe.
Forward fix (separate PR): publish a new thin script that, alongside the
existing GPG setup, writes a `gpg.program` wrapper using `--pinentry-mode
loopback --passphrase-file` so signing is independent of the agent's cache
state. Re-bump past 28231 then.
Fixes WIN-1974
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(git-sync): check in source-of-truth for the next hub script (gpg.program wrapper)
This is the script that will be published to hub.windmill.dev once verified
on a customer GPG-signed deploy. It replaces hub/28231's agent-cache
pre-warm (`gpg -bsau` with --passphrase) with a stateless gpg.program
wrapper + chmod-600 passphrase file. Every git-invoked gpg call goes
through the wrapper, which always uses --pinentry-mode loopback (and
--passphrase-file when a passphrase exists). Signing no longer depends on
gpg-agent having a cached passphrase by the time the CLI's `git commit`
runs — which closes WIN-1974.
Not wired in yet: LATEST_GIT_SYNC_SCRIPT_PATH stays on hub/28230 until this
script is uploaded and the new hub id is known. This file is checked in so
the diff is reviewable, future bumps have a source of truth, and a CLI
regression test can `cat` it for fixture parity.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): skip format/pattern validation for $var/$res/$jsonvar references in ArgInput
A resource field with a `pattern` constraint (e.g. the gpg_key.private_key
field, whose pattern enforces a `-----BEGIN PGP PRIVATE KEY BLOCK-----`
prefix) rejects values like `$var:u/me/gpg-private-key` with an "invalid
format" error in the resource editor — even though `$var:`/`$res:`/`$jsonvar:`
are placeholders the backend resolves at runtime, not the actual string
that needs to match the regex.
Bail out of all format/pattern checks (email, ipv4, ipv6, uuid, custom
pattern) when the value is one of these references. Required/numeric
bounds/array checks still apply since they're shape-level, not regex.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(git-sync): bump LATEST_GIT_SYNC_SCRIPT_PATH to hub/28234 (gpg.program-wrapper fix)
hub/28234 is the forward fix for WIN-1974: replaces hub/28231's agent-cache
pre-warm (which became stale by the time the CLI's `git commit` ran) with
a stateless `gpg.program` wrapper that uses `--pinentry-mode loopback`
(and `--passphrase-file` when a passphrase exists) on every gpg invocation.
Bundled CLI is windmill-cli@1.705.0.
Verified via reproducer at /tmp/git-sync-diff/test-gpg-fix.sh: deliberately
killing gpg-agent between GPG setup and `git commit` reproduces the
customer's `gpg failed to sign the data` error verbatim under the old
flow, and the wrapper signs through it. Holds for passphrase-protected
keys, split-subkey [C]+[S] layouts, and unprotected keys.
Drops the local source-of-truth copy (`hub-scripts/`) — hub is canonical
now that 28234 is published.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(git-sync): drop verbose comment above LATEST_GIT_SYNC_SCRIPT_PATH
The git history (this PR) carries the why; the constant name + value carry
the what.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Reduce slim image vulnerability surface
* chore(docker): drop apt-get upgrade -y from slim images
apt-get upgrade hurts build reproducibility (same Dockerfile + same
commit at different times produces divergent images) and trips hadolint
DL3005. The freshness it buys is dominated by simply rebuilding against
the periodically-refreshed debian:bookworm-slim base image.
The --no-install-recommends and apt-list cleanup wins are kept.
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
The disk_backed_refuses_preexisting_symlink_at_jail_tmp test calls
std::os::unix::fs::symlink directly, which doesn't exist on Windows
targets. Without a cfg gate, `cargo check --tests` fails on Windows
with E0433. Other symlink call sites in this crate (php_executor,
bun_executor, rust_executor, etc.) already follow this pattern.
Fixes WIN-1972
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(nsjail): optional disk-backed /tmp via instance setting
* test(nsjail): unit-test tmp mount resolver and narrow visibility
* refactor(nsjail): switch tmp backing to select + conditional UI
* ui(nsjail): make tmpfs the visible default in /tmp backing select
* fix(nsjail): refuse preexisting jail_tmp to block symlink escape
* fix(nsjail): allow jail_tmp reuse on sequential nsjail calls
Codex flagged that python/ruby/rust executors invoke nsjail twice per
job_dir (install then run). The previous resolver treated any preexisting
jail_tmp as hostile and silently fell back to tmpfs on the second call,
so disk-backed mode never reached the main script run for those langs.
Use symlink_metadata().is_dir() to distinguish a real directory left by
an earlier call in the same job_dir (safe to reuse) from a symlink or
other entity (still refused, as the codebase-tar escape requires).
Also loosen the frontend visibility predicate: only hide nsjail settings
when job_isolation is explicitly 'none' or 'unshare', so deployments
that enable nsjail via DISABLE_NSJAIL=false with no DB setting can
still see the controls.
* fix(ai): enforce RLS and scope check on user-supplied X-Resource-Path
The AI proxy handler accepts an X-Resource-Path header to override the
configured workspace AI provider. When supplied, the handler loaded the
resource value from the resource table using the root DB pool with no
resources:read scope check, so any authenticated workspace user could
point X-Resource-Path at a restricted AI resource (e.g. one in a folder
they cannot read) and the proxy would use that resource's provider
credentials for the outbound AI request.
For user-supplied resource paths, now require resources:read:{path}
scope and fetch the resource through user_db.begin(&authed) so RLS
enforces the same folder/group boundary as the resource API. The RLS-
scoped $var: resolution stays in place as defense in depth. The
admin-configured workspace/instance ai_config path is unchanged.
Fixes WIN-1971
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(ai): regression test for X-Resource-Path RLS enforcement
Cover all four cases:
- non-admin pointing X-Resource-Path at a restricted resource is rejected
- non-admin pointing it at a resource they own still works
- admin can point it at any resource
- workspace-configured proxy flow (no X-Resource-Path) is unchanged
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the main Windmill Dockerfile pattern: creates a windmill user
(UID/GID 1000) and makes cache/work directories world-writable so the
image runs cleanly under Kubernetes securityContext.runAsNonRoot or
runAsUser: 1000 without permission errors on Bun, pip, or windmill
cache writes.
Fixes WIN-1969
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): add `wmill init prompts` and custom override slot
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): replace init prompts with refresh prompts + AGENTS.md/AGENTS.cli.md split
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): dedupe claude skills via @-includes and add prompts freshness check
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): drop migration-choice flags from `refresh prompts`
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(cli): add 'Running and previewing local changes' section to AGENTS.cli.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): write full skill content to .claude/, drop @-include wrapper
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): reconcile CLAUDE.md the same way as AGENTS.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): address PR review nits — argv parsing, lazy import, comment detection, error propagation
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(indexer): tell admins when ingress routes search to wrong pod
When the IndexReader is absent on the pod handling a search request but
another pod is actively holding the indexer lock, the EE handler now
returns a tailored error pointing at the ingress/load-balancer
configuration instead of the generic "indexer not running" message.
The indexer status endpoint reads the DB lock so it reports "running"
from any pod, but search endpoints need the in-memory IndexReader that
only exists on the lock holder. In multi-replica deployments this looks
like the indexer is healthy but every search 404s.
Companion: windmill-labs/windmill-ee-private#TBD
Fixes WIN-1968.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to eb18d7b4c0e37fea3f6e1e2cc44e0fddd74ff817
This commit updates the EE repository reference after PR #586 was merged in windmill-ee-private.
Previous ee-repo-ref: 7dd43d1850813071cc18ba49ba090583e7321f4b
New ee-repo-ref: eb18d7b4c0e37fea3f6e1e2cc44e0fddd74ff817
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>
* feat: add UV_PYTHON_INSTALL_MIRROR env and instance setting
Allows operators to point `uv python install` at a private mirror of the
python-build-standalone releases. Configurable via the
`UV_PYTHON_INSTALL_MIRROR` env var or the `uv_python_install_mirror`
instance setting, with the env var as the boot fallback and the instance
setting taking precedence at reload.
Fixes WIN-1966
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: hoist uv_python_install_mirror binding above sandboxing branch
The non-sandboxed uv pip install branch referenced a binding that was
only declared inside the sandboxed branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: neutral placeholder for uv_python_install_mirror
The previous placeholder was the default public URL the setting is meant
to redirect away from. A neutral example mirror URL is clearer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): add datatable list and run commands
* feat(cli): render datatable query results as a table
* feat(cli): serve datatables as a postgres-wire endpoint
* feat(cli): add 'datatable psql' to launch psql against the proxy
* feat(cli): route datatable serve by client-supplied database name
* override database list + password option
* fix: support extended queries in datatable serve
* fix: correct cloud size threshold log and parse CLI descriptions with parens/trailing comma
* refactor: extract raw_output envelope encoding into pg_raw_output module
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
* fix(flows): flag noLogs jobs and lazily resolve them in log panel
* fix appending to flag
* fix: preserve WM_LOGS_SKIPPED sentinel on SSE/replay completion
pickMoreCompleteLogs resolved both sentinel and undefined to '', so the
SSE completion event (whose job field is fetched .without_logs()) would
clobber the sentinel placed by flagSkippedLogs. The module log panel
then saw '' instead of the sentinel, defeating the lazy-resolve path.
Also wire onLogsResolved on the OutputPickerInner inline LogViewer so a
lazy resolve writes back to flowStateStore.previewLogs, matching
ModulePreviewResultViewer and avoiding repeated fetches on remount.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): remove localStorage-backed autosave drafts
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(frontend): add UserDraft service for per-workspace local drafts
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>
* tests
* nit schedule_ prefix
* feat(frontend): persist deep mutations in useLocalStorageValue
Track the serialized value alongside the $state and add an $effect that
deep-reads it (via readFieldsRecursively). When a deep mutation produces
a serialization that differs from the last persisted blob, write it to
localStorage. The setter keeps writing synchronously so callers reading
localStorage right after assignment still see the new value; the effect
no-ops on those because lastSerialized was already updated by the setter.
Undefined values are persisted as a removal.
UserDraft no longer needs its own removeItem workarounds for undefined
values — useLocalStorageValue handles that uniformly now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): add defaultValue + empty-path handling to UserDraft
UserDraft.use() accepts an opts.defaultValue used when no localStorage
entry exists yet. It is not persisted on first read — only an actual
mutation writes through.
Empty paths (new items) bypass localStorage entirely. The entry still
lives in the in-memory Map so multiple components on the same /add page
share state, but save/get/remove/use never read or write localStorage
with an empty path. Once the item is saved and the route navigates to
its new URL, a fresh use() on the non-empty path takes over.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire script editor to UserDraft
The script editor's top-level state now lives in UserDraft.use(), keyed
on the route's path (page.params.path on /scripts/edit, '' on /scripts/add).
Deep edits inside ScriptBuilder persist automatically; deploy and draft
restore now call UserDraft.remove to clear the local autosave alongside
the backend draft.
Replaces the URL-hash autosave that ScriptBuilder used to write via
replaceStateFn — that prop is now gone, the encodeScriptState debounce
is gone, and Triggers no longer takes a saveSessionDraft callback.
Viewing a specific historical hash (?hash=...) is kept draft-free by
passing '' as the path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire flow editor to UserDraft
flows/add and flows/edit drive the flow value through a StateStore
adapter backed by UserDraft.use, so every edit auto-persists at
userdraft/w/{ws}/flow/{path} without touching FlowBuilder's internal
.val convention. On returning visits the local autosave wins and a
toast offers a diff against the latest backend draft/deployed version;
on a fresh visit the backend value is written into the handle. Deploy,
save-as-draft rename, restore-draft and restore-deployed each call
UserDraft.remove on the route path so the local autosave doesn't
outlive the action.
Adds UserDraft.has() for "is there already a local draft?" detection
in the load path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire app editor to UserDraft
AppEditor registers a UserDraft.use<App> handle for its current path
(empty path for /apps/add stays in-memory) and a single $effect
deep-tracks the internal stateApp and forwards every mutation to the
handle. useLocalStorageValue's lastSerialized check then dedupes the
actual localStorage writes per tick, so even fast drag/resize loops
only persist when the JSON output really changes.
/apps/edit overlays a local autosave from UserDraft.get on top of the
backend value when one exists, with the existing "Discard / Show diff"
toast wired to UserDraft.remove. Deploy, save-as-draft, restore-draft
and restore-deployed all call UserDraft.remove on the relevant path,
including the JSON editor save paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire raw app editor to UserDraft
/apps_raw/edit owns the canonical raw-app state (files, runnables,
data, summary) in four $state vars; a single $effect deep-tracks them
and forwards the bundle to a UserDraft.use<RawAppDraft> handle so each
mutation tick persists at userdraft/w/{ws}/raw_app/{path} (deduped by
useLocalStorageValue's serialized check). On load the route overlays
the local autosave on top of backend.draft/deployed and offers a
"Discard / Show diff" toast when they diverge; matching local entries
are silently dropped. Deploy, save-as-draft rename, restore-draft and
restore-deployed each call UserDraft.remove on the route path.
/apps_raw/add keeps the same shape (UserDraft.use with empty path)
so the draft is in-memory only and we drop it explicitly when the
initial save creates the real path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire resource editor to UserDraft
ResourceEditor registers a UserDraft.use<ResourceState> handle keyed
on the initialPath (empty for new resources, in-memory only). A
$effect deep-tracks the current workspace's edit state and forwards
mutations to the handle; on bootstrap and lazy backend-fetch the
local autosave wins over the backend value when they diverge. After
a successful save() we call UserDraft.remove so the local autosave
doesn't outlive the deploy. Cross-workspace deploys always start from
the live backend value rather than the local draft.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire variable editor to UserDraft
VariableEditor persists the current workspace's edit state via
UserDraft.save on every mutation, keyed on editPath ('' for new
variables → in-memory only). Backend fetches now overlay a matching
local autosave when one exists, and initNew() rehydrates from the
in-memory empty-path entry so opening a fresh "Add variable" drawer
keeps any unsaved work from the previous open. After a successful
save we drop the corresponding entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* editor external changes sync
* fix(frontend): don't UserDraft.remove flows while route is still mounted
The /flows/add and /flows/edit routes drive FlowBuilder from a flowStore
whose getter reads flowHandle.draft directly. Calling UserDraft.remove
synchronously before goto() therefore wiped the in-memory entry, made
flowStore.val collapse to emptyFlow(), and tripped
UnsavedConfirmationModal against the just-saved value — even though the
deploy/save-draft itself succeeded.
Drop those explicit removes in onSaveInitial, /add onDeploy, and
/edit onDeploy. The empty-path entry self-cleans on unmount via
onDestroy ref counting; for the non-empty edit path the next visit's
load-time diff will silently overwrite localStorage when the local
autosave matches the deployed value. Restore-draft/restore-deployed
keep their explicit remove because they navigate to the same route
(no modal) and loadFlow immediately rehydrates the handle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "fix(frontend): don't UserDraft.remove flows while route is still mounted"
This reverts commit 079ebef72b.
* Only remove from localStorage
* feat(frontend): saveInitialValue option on useLocalStorageValue
The first time a value flows into a UserDraft.use() handle — typically
the editor route loading the backend value via flowHandle.draft =
backendFlow — is the baseline, not a user edit. Persisting it on the
spot puts a copy of the backend into localStorage on every page open
and produces spurious "local autosave" toasts on next visit when the
serialization round-trips differently.
useLocalStorageValue now takes options.saveInitialValue (default true,
backward compatible). When false, the first time the serialised form
of the state changes — via the setter or via a deep mutation — the
lastSerialized cache is updated but localStorage is not touched. Every
write after that persists normally. UserDraft.use() passes false.
Tests updated to reflect the new contract (first write is the
baseline) and a regression test added for the second-write-persists
behaviour.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): persist full multi-workspace bundle for resources/variables
ResourceEditor and VariableEditor can stage edits for several target
workspaces in a single drawer session (see deployTo / states[ws] map).
The previous UserDraft wiring only persisted states[$workspaceStore] —
the user's session workspace — so any edit made under a different
target workspace tab disappeared on refresh.
Persist the entire `states: Record<wsId, State>` bundle as the draft
value instead. On lazy-fetch we pick the local state for that ws if
present and divergent from the backend; on bootstrap for new
resources/variables we restore states for every workspace the user
had staged. The localStorage key still lives under the user's session
workspace via UserDraft, but its contents now cover all target
workspaces from that session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): bake parent_hash into the initial script load
loadScript() assigned the backend value to scriptHandle.draft and then
deep-mutated parent_hash on the next line. Under
useLocalStorageValue's saveInitialValue=false contract only the very
first write is the baseline — the parent_hash mutation right after
counted as a second write and was persisted to localStorage, so
opening an existing script would silently write a draft entry even
though the user hadn't touched anything.
Combine `parent_hash` (and the topHash override) into a single
bakedBaseline so each branch of loadScript performs exactly one
assignment to scriptHandle.draft. Mirrored across the local-autosave
branch's discard callbacks too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire SqsTrigger editor to UserDraft
Persist the trigger's getSaveCfg() output to
userdraft/w/{ws}/schedule_sqs/{path} on every edit, overlay any
existing local autosave on top of the backend value when openEdit
loads the trigger, and clear the entry on successful update.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire KafkaTrigger editor to UserDraft
Same pattern as the Sqs trigger: persist getSaveCfg() on every edit,
overlay any local autosave on top of the backend value when openEdit
loads the trigger, drop the entry on successful update.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire NatsTrigger editor to UserDraft
Same pattern as the Kafka trigger: persist getSaveCfg() on every edit,
overlay any local autosave on top of the backend value when openEdit
loads the trigger (with initialConfig/originalConfig snapshotted from
backend first so hasChanged correctly reports the overlay as unsaved),
drop the entry on successful update.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire MqttTrigger editor to UserDraft
Same pattern: persist getSaveCfg() on edits, overlay local autosave
in openEdit (with initialConfig/originalConfig snapshotted from
backend first), drop the entry on successful update.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire GcpTrigger editor to UserDraft
Same pattern as the other triggers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire AzureTrigger editor to UserDraft
Same pattern as the other triggers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire WebsocketTrigger editor to UserDraft
Same pattern as the other triggers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire PostgresTrigger editor to UserDraft
Same pattern as the other triggers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire EmailTrigger editor to UserDraft
Same pattern as the other triggers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire HTTP RouteEditor to UserDraft
Same pattern as the other triggers, keyed on schedule_http.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): wire ScheduleEditor to UserDraft
Same pattern, keyed on schedule_schedule. ScheduleEditor doesn't track
an originalConfig (its saveDisabled doesn't compare against a baseline)
so ordering is simpler — initialConfig snapshotted from backend, local
autosave overlaid after.
This completes UserDraft wiring across all 11 trigger editors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): rename schedule_* UserDraft kinds to trigger_*
The schedule_ prefix grouped all the trigger editors under what looked
like a "scheduler" namespace; trigger_ is what these actually are
(triggers — including the cron-style schedule). Mechanical rename
across UserDraftItemKind, every trigger editor's UserDraft.save/get/
remove calls, and the one test that asserted on the localStorage key.
Behaviour-only impact: existing localStorage keys under
userdraft/w/{ws}/schedule_{kind}/{path} from older builds will be
ignored on next open (no schema migration). Users will lose any
unsaved trigger drafts persisted before this change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): wrap UserDraft localStorage payload as { value }
localStorage entries now look like {"value": <draft>} instead of just
<draft>. The wrapping is invisible at the API boundary — UserDraft.use,
.save, .get, .remove all still operate on the unwrapped draft value —
but it leaves room to add metadata (timestamps, originating user,
schema version, ...) later without breaking existing entries.
Internals:
- StoredDraft<V> = { value: V } is what we serialise to localStorage
and what useLocalStorageValue's $state holds.
- wrap()/unwrap() helpers gate the boundary; the handle returned by
use() unwraps on get and wraps on set.
- readPersisted() defensively drops entries whose payload isn't a
{ value: ... } object, so pre-migration drafts written by earlier
commits on this branch are simply ignored (has() returns false,
get() returns undefined) rather than confusingly surfacing as
undefined-shaped drafts.
Test data switched from { value: X } (which collides confusingly with
the wrapper shape) to plain primitives / objects, plus a regression
test for the pre-migration ignore behaviour. 28 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(backend): expose freshness for UserDraft staleness check
Variable
- Add `edited_at TIMESTAMPTZ NOT NULL DEFAULT now()` + `edited_by VARCHAR(50)` to the `variable` table (parity with `resource`); set them on INSERT and on every UPDATE.
- Surface them on `ListableVariable` so `getVariable` / `listVariable` return them.
DB drafts (script, flow, app/raw_app)
- The `*WithDraft` endpoints now also return `draft.created_at` as `draft_created_at`. The draft value alone wasn't enough to tell whether a teammate (or another tab) had pushed a fresh draft while local autosave was in flight; the new field is the staleness signal.
- Wired in `get_script_by_path_w_draft` (`ScriptWDraft.draft_created_at`, including the `prefetch_cached` forwarding), `get_flow_by_path_w_draft` (`FlowWDraft.draft_created_at`), and `get_app_w_draft` (`AppWithLastVersionAndDraft.draft_created_at`). OpenAPI updated to match.
The frontend will read these in a follow-up to implement the local-draft staleness check; this commit only widens the API surface.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): track remote rev metadata on UserDraft entries
Extends StoredDraft<V> with two optional rev fields used by the
forthcoming staleness modal:
- remoteRev — the deployed version's id/hash/timestamp at the moment
the local draft was created. Compared against the latest deployed
rev on reload.
- remoteDraftRev — the DB-draft created_at at the moment the local
draft was created. Only meaningful for kinds that have a DB draft
(script, flow, app, raw_app). Checked first so a teammate's draft
push is detected before the "deployed version moved" case.
API additions on the handle returned by UserDraft.use():
- handle.meta — read the rev metadata currently stored.
- handle.setDraftAndMeta(value, meta) — atomic write of value + meta in
a single state.val assignment. Editor routes use this on load so the
baseline rev rides along with the value without consuming the
saveInitialValue=false dedup slot twice.
- handle.setMeta(meta) — update just the rev metadata after the user
picks "Keep current draft" in the staleness modal.
- handle.draft = X — unchanged surface; now preserves existing rev
metadata across user edits.
Plus UserDraft.getMeta() and UserDraft.save() preserves any persisted
rev metadata when called without a live handle.
7 new tests cover the metadata surface; all 35 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): staleness modal for the script editor's local autosave
Replace the script editor's toast-based "Discard / Show diff" pattern
with a dedicated modal that surfaces *why* the local autosave is out
of date: a new DB draft on the server, or a new deployed version.
Adds `checkStaleness` (UserDraftMeta vs current backend revs, draft-rev
priority) and a `setMeta({ force: true })` mode so the "Keep current
draft" acknowledgement persists even when it happens to be the
entry's first state mutation — under `saveInitialValue: false` an
ack-only setMeta would otherwise be skipped and the modal would
re-fire on next mount.
The modal lives at LocalDraftStaleModal.svelte; the script editor
wires it as a template for the remaining editors. Other editors
(flows, apps, raw_apps, resources, variables, triggers) still use
the previous toast pattern and will be migrated in follow-up
commits.
* feat(frontend): staleness modal for flow, app, and raw-app editors
Migrates the flow, app, and raw_app editor routes to the same
`LocalDraftStaleModal` flow already used by scripts: compare the
recorded meta against the current `version` / `versions[last]` and
`draft_created_at`; on mismatch, surface the choice in a modal.
Adds `UserDraft.saveMeta` for routes that don't hold a live handle
(the app editor reads via `UserDraft.get` and the handle lives in
the child `AppEditor` component). It writes meta directly to
localStorage and tolerates the no-entry case.
* feat(frontend): migrate legacy localStorage autosave entries
Apps and flows used to autosave under un-scoped keys (`flow`/`flow-{path}`,
`app`/`app-{path}`, `rawapp`/`rawapp-{path}`) with a base64-encoded
state envelope. This adds a one-off migration that rewrites surviving
legacy entries under the workspace-scoped `userdraft/w/{ws}/{kind}/{path}`
keys with the new `{ value }` wrapper, transforms the payload where the
shape differs (drops the flow view-state envelope, defaults the new
raw-app `summary` field), and drops the source key.
The migration lives in its own file (`userDraftLegacyMigration.ts`)
so the new UserDraft service stays free of legacy decoders. Idempotent
via a `userdraft/legacy_migrated_v1` sentinel; runs from the logged-in
root layout once a workspace is known. Defensive shape checks avoid
clobbering co-resident apps that happen to use the same key prefixes.
* nit remove comments
* refactor(frontend): per-workspace UserDraft handles in Resource/Variable editors
Earlier commits in this PR wired the resource and variable editors to a
single multi-workspace bundle stored under the user's session workspace
key — which mixed workspaces in one localStorage entry and required a
custom multi-key fix-up pass to persist edits for other workspaces.
Reset both editors to their pre-PR shape and apply the minimal change:
the per-workspace `Record<string, ResourceState>` (resp. `VariableState`)
becomes `Record<string, UserDraftHandle<…>>`, with one handle per
workspace created via `UserDraft.use(…, { workspace: ws })`. The handle
keys its own localStorage entry under that workspace, so cross-workspace
edits stay cleanly separated and reactivity flows through the handle's
`draft` accessor — `bind:` on form fields just works.
Adds `manualRelease: true` + `handle.release()` to `UserDraft.use` so
the editors can register handles lazily inside an effect (Svelte 5
forbids `onDestroy` outside component init). The editors register a
single top-level `onDestroy` that releases every collected handle.
After a successful save, the per-workspace autosave is cleared via
`UserDraft.remove(itemKind, path, { workspace })`.
* refactor(frontend): seed per-workspace handles via UserDraft.use defaultValue
ensureHandle was doing a post-hoc `if (h.draft === undefined) h.draft = baseline`,
which relies on the saveInitialValue=false skip to swallow that seeding
write. Hand the baseline to `UserDraft.use({ defaultValue })` instead —
useLocalStorageValue uses it as the initial $state value when localStorage
is empty, so lastSerialized is correct out of the gate and no setter call
is needed.
* feat(frontend): persist empty-path drafts across reloads
Empty paths used to be in-memory only (via the `isLocalOnly` short-circuit)
because we worried about collisions between concurrent /add tabs. The user
asked for the trade-off to flip: a /flows/add or /scripts/add reload should
restore the user's work, while explicitly clicking "+ Flow / + Script / …"
should always open a clean editor.
- Drop `isLocalOnly` from UserDraft so empty-path entries persist under
`userdraft/w/{ws}/{kind}/` like any other path. The existing per-kind
refcounting and saveInitialValue=false behavior already handle them
correctly — the change is just lifting the bypass.
- Each /add page now calls `UserDraft.remove(kind, '')` synchronously
when `?nodraft=true` is present in the URL, before the handle is
created.
- The two "+" entry points that lacked the `?nodraft=true` flag
(CreateActionsScript's plain `<a href>` and CreateActionsFlow's
YAML/JSON import paths) now include it, so every fresh-start path goes
through the wipe.
- Tests updated: the "empty path (in-memory only)" block becomes
"empty path (persists across reloads)" and asserts the new behavior.
* refactor(frontend): drop legacy-migration shape guard
We assume Windmill is the only app on the origin, so the
isPlausibleLegacyValue per-kind shape check was just dead weight.
Keep the cheap "decoded is an object" guard for malformed payloads.
* docs(frontend): refresh stale "in-memory only" comments around empty paths
Empty-path UserDraft entries persist now. Drop the leftover "in-memory
only" comments on the /add pages' handle creation, and rewrite the
EditorHeader save-initial-draft comments to describe why the UserDraft.remove
call is still needed: the draft was promoted to a real path on the
backend, so the prior-path autosave must not shadow a future "+ App" /
"+ Flow" / … visit.
* fix(frontend): strip ?nodraft=true from /add URLs synchronously
The previous cleanup ran in afterNavigate, which (a) fires asynchronously
— a quick reload between mount and the callback would re-wipe the
freshly-started draft — and (b) did `url.search = ''`, nuking sibling
params like ?template, ?hub, and ?wac.
Move the URL cleanup to the same synchronous block that calls
UserDraft.remove on nodraft, using `window.history.replaceState` so it
lands before paint. Only the `nodraft` key is removed — other params
survive.
* feat(frontend): toast when editor opens on a local autosave
When a route loads its local autosave (differs from backend, no
staleness alarm), surface "Restored from local storage" with up to
two reset actions:
- "Reset to saved draft": drop the autosave, reapply the backend DB
draft. Only shown when the backend has a DB draft.
- "Reset to deployed": drop the autosave, delete the DB draft on the
backend (if any), reload from the deployed version. Only shown when
the item has a deployed version.
The toast title + label wording + per-state inclusion live in a
single helper (`$lib/userDraftToast`). Each editor passes its own
reset callbacks since the side effects differ per route (handle vs
UserDraft.get/save, redraw counters, loadXxx helpers).
Wired to scripts/edit, flows/edit, apps/edit, apps_raw/edit. Resource
and variable editors don't have DB drafts and use per-workspace
handles — a follow-up will tailor a single-action version.
* feat(frontend): load URL-encoded scripts on /scripts/add
The "Fork" action on run/[...run] and several workspace-settings
helper-script templates base64-JSON-encode a NewScript into the URL
hash on `/scripts/add#...`. Until now /scripts/add silently dropped
that payload — both call sites landed on a blank editor.
Decode `page.url.hash` at module top, and if it parses to an object,
apply it as `scriptHandle.draft` and surface "Loaded from URL". The
URL value wins over local autosave, ?template, ?hub, and YAML imports
because the hash represents an explicit "open this script" intent.
Parsing is inlined rather than reusing `decodeState` so an unrelated
hash (e.g. a future route anchor) doesn't fire its default "Impossible
to parse state" error toast.
* feat(frontend): strip URL hash from /scripts/add after consumption
The URL-encoded script is a one-shot seed (Fork preview, workspace
handler templates, hub publish) — keeping the hash in the bar after
loading meant a reload would re-apply the original payload and wipe
whatever the user edited since landing.
After applying `urlScript` and firing the "Loaded from URL" toast,
clear `location.hash` via `window.history.replaceState`. The user's
edits then flow into the normal autosave path (UserDraft empty-path
entry), and a reload restores those edits instead of the seed.
* feat(frontend): load URL-encoded scripts on /scripts/edit + consume-once
Mirror the URL-hash seed mechanism from /scripts/add to /scripts/edit
for parity: decode the base64-JSON-encoded NewScript payload from the
URL hash, apply it over the bakedBaseline as the editor's initial
state, send "Loaded from URL", and strip the hash immediately via
window.history.replaceState so a reload restores the user's autosave
rather than re-injecting the seed.
The seed wins over local autosave + backend draft + deployed —
UserDraft.remove(script, draftPath) drops the stale autosave on disk
before setDraftAndMeta writes the seeded value, so the user's
subsequent edits will overwrite cleanly.
Skipped when ?hash= is in the URL (historical-version view, which is
read-only relative to drafts) and when the hash fragment isn't a
parseable encoded payload.
No callers build /scripts/edit#<encoded> URLs today — this lands the
mechanism for future symmetry with /scripts/add.
* fix(frontend): "Reset to deployed" loop on Restored-from-local toast
UserDraft.remove only clears localStorage — the entry's reactive cell
stays alive as long as some component holds a handle. The toast
callback was relying on remove+loadXxx to reset state, but loadXxx
then read the *in-memory* autosave through the still-alive entry,
matched it against the now-deployed reference, and re-fired the same
toast. Forever.
Drop the in-memory state explicitly before the load:
- scripts/flows/apps_raw (route-level handle): `handle.setDraftAndMeta(undefined, {})`
- apps (handle lives in the AppEditor child): set `app = undefined`
to unmount AppEditor — its onDestroy releases the handle and the
entry's refcount drops to 0, destroying the entry.
ScriptBuilder / FlowBuilder / RawAppEditor briefly unmount while the
reload fetches; the flash is the user-visible "loading" cue.
* fix(backend): convert draft.created_at to TIMESTAMPTZ
The new `*WithDraft` endpoints surface `draft.created_at` as
`Option<chrono::DateTime<Utc>>` for the frontend's staleness check,
which requires `TIMESTAMPTZ`. The column was originally created as
plain `TIMESTAMP`, so SQLx fails to deserialize any row that has a
non-null draft and the handler returns HTTP 400 instead of 200 —
caught by `test_draft_endpoints` in the integration tests.
Migrate the column to `TIMESTAMPTZ`, interpreting existing values as
UTC (matching `now()`'s behaviour on a UTC server). No compile-time
sqlx queries reference the column, so the offline cache stays valid.
* fix(frontend): settings drawer auto-opening on /scripts/edit
ScriptBuilder's metadataOpen flag fires when `initialPath == ''` (the
heuristic for "new script, expected on /scripts/add"). The route's
`let initialPath = $state('')` left it empty until applyBaseline ran
later inside loadScript.
Pre-PR, the editor was gated on a route-level `script` $state that
started undefined, so ScriptBuilder didn't mount until loadScript's
synchronous block set both `script` and `initialPath` in the same
tick. With UserDraft.use reading localStorage synchronously, the gate
(`scriptHandle.draft`) is satisfied at mount time and ScriptBuilder
mounts with the still-empty initialPath, popping the drawer open.
Seed initialPath from page.params.path synchronously so ScriptBuilder
sees the path on its first render. Falls back to '' for the historical
`?hash=` view to preserve the existing behaviour there.
* fix(backend): refresh draft.created_at on every upsert
The draft upsert was `ON CONFLICT (...) DO UPDATE SET value = EXCLUDED.value`,
so subsequent draft writes left `created_at` frozen at the first INSERT.
The frontend's UserDraft staleness check reads that timestamp as
`remoteDraftRev`; with it frozen, an updated remote draft looked
identical to the originally-baselined one and the "newer draft was
saved on the server" modal never fired.
Touch `created_at` on conflict too. The column's semantic widens from
"first write time" to "last write time", which is what every reader of
the field actually wants — the staleness signal is the only consumer.
SQLx offline cache regenerated to match the new query text.
* fix(frontend): persist trigger drafts in script-editor autosave
The triggers in ScriptBuilder live in a dedicated `triggersState`
$state, separate from the `script` object that the UserDraft handle
deep-tracks. Pre-PR the per-builder localStorage autosave bridged the
two by snapshotting `triggersState.getDraftTriggersSnapshot()` into
the payload on every write — that bridge was dropped when we removed
the per-builder autosave in favour of UserDraft.
Add an $effect that deep-reads triggersState and mirrors the snapshot
back into `script.draft_triggers`. The UserDraft handle (already
deep-tracking `script`) then persists the trigger drafts as part of
the script autosave, restoring the prior behaviour.
* feat(frontend): debounce option on useLocalStorageValue + 500 ms in UserDraft.use
Adds `debounce: number` to `useLocalStorageValue`'s options. When set,
repeated mutations within the window collapse into a single
localStorage write fired by a plain `setTimeout`. The in-memory
`$state` is updated on every change so readers of `.val` always see
the latest value; only the persistence side-effect is deferred.
No `onDestroy` flush — the timer is independent of the Svelte
lifecycle, so SPA route teardown doesn't drop the pending write
(the callback still fires later as long as the JS context is alive).
A hard browser tab close within the window does drop it; that's an
acceptable trade-off vs the complexity of `beforeunload` listeners
and the leak/refcount issues they create alongside `useLocalStorageValue`'s
keyed instances.
`UserDraft.use` opts in with `debounce: 500` so a typing storm in the
script/flow/app editor produces one localStorage write per 500 ms
instead of one per keystroke.
Tests switch to `vi.useFakeTimers()` and a `flushPersist()` helper to
keep the synchronous `expect(localStorage…)` assertions working. New
test verifies the coalescing behaviour end-to-end.
* fix(frontend): tighten legacy-migration key matching
The legacy migration was consuming any localStorage key starting with
`app-`, `flow-`, or `rawapp-`, with no constraint on what followed and
no shape check on the decoded payload. Two failure modes called out
in review:
1. A future feature (or third-party extension) picking a name like
`app-recent` would silently lose data on first migration run.
2. A stray key that happened to base64-decode to valid JSON but
wasn't a real legacy draft would still get promoted to the new
format, surfacing later as a phantom "Restored from local storage"
toast on the next edit.
Two guards:
- `LEGACY_PATH_SHAPE = /^[uf]\/[^/]+\/.+$/`: after a `<prefix>-` match,
the remainder must look like a Windmill item path (`u/owner/name`
or `f/folder/name`, possibly with deeper segments). Bare-prefix
empty-path entries (`app` / `flow` / `rawapp` for `/add` autosaves)
still match the exact branch and don't go through the shape gate.
- `isPlausibleLegacyValue`: after decode, require the payload to
carry the field the legacy writers actually produced
(`flow.flow` for flows, any of `summary|value|policy|path` for
apps, any of `files|runnables|data` for raw apps).
Both are belt-and-suspenders: nothing else currently uses these key
prefixes, but enforcing the shape locally keeps the migration safe
against future namespace collisions.
* fix(backend): drop AT TIME ZONE 'UTC' from draft.created_at migration
The original migration forced `USING created_at AT TIME ZONE 'UTC'`,
which tags every existing wall-clock value as UTC. That matches the
common case (Postgres on a UTC server, which the Docker image and most
managed offerings default to), but on a non-UTC operator's deployment
it shifts all pre-migration timestamps by the server's tz offset.
Drop the USING clause. Postgres's default `TIMESTAMP -> TIMESTAMPTZ`
cast reinterprets each existing value in the session's current
timezone — which is the same timezone under which the original
`INSERT ... DEFAULT now()` values were truncated to TIMESTAMP, so
the conversion correctly recovers the original instant regardless of
the operator's timezone. Same semantics on UTC servers, correct
semantics on non-UTC servers.
Down migration updated symmetrically.
* docs(frontend): clarify staleness modal copy
The four route-level editors (scripts/flows/apps/apps_raw) keep the
user's local draft visible behind the modal so they can glance at it
before choosing. The old body text described the situation (server
has moved on, local autosave is behind) but didn't say what's
actually on screen or how each action maps to it.
New body leads with "The editor is showing your local autosave" and
spells out each action: "Load latest replaces what's on screen; Keep
current leaves it alone." Same copy for both `cause = 'draft'` and
`cause = 'version'`, branching only on what the user is "behind"
relative to.
* refactor(frontend): drop dead updateDraftCallback from Triggers constructor
None of the eight `new Triggers(...)` call sites pass an update
callback any more — the bridge was a leftover from the pre-UserDraft
era when ScriptBuilder ran its own localStorage autosave and had to
be notified on every triggers mutation. The unified UserDraft handle
now deep-tracks `script.draft_triggers` via the $effect in
ScriptBuilder, so the callback channel is dead weight.
Removes the third constructor parameter, the private field, and the
six `this.#updateDraftCallback?.()` invocations across setters and
mutators.
* docs: review nits — variable.edited_at backfill, UserDraft toast/modal headers
Three low-priority callouts:
- Document the variable.edited_at backfill in the migration. All
existing rows get a single `now()` timestamp from the column
DEFAULT; the staleness check only consumes the field as an opaque
rev string and never displays/sorts on it, so the collision is
harmless — but worth saying out loud.
- Add module headers to userDraftToast.ts and LocalDraftStaleModal.svelte
explaining how this layer sits above the per-browser UserDraft
autosave and is distinct from the backend DraftService (the
server-side "Save as draft" feature surfaced as `*.draft`).
* refactor(frontend): replace UserDraft.release() with useMany()
Public surface change:
- New `UserDraft.useMany(getSpecs: () => UserDraftSpec<V>[])` returns a
reactive array of handles. The reconcile loop acquires entries for
added specs, releases entries for removed specs, and re-uses cached
handles for unchanged keys so caller-captured references stay stable.
- `UserDraft.use(kind, path, opts?)` becomes a 1-len wrapper around
`useMany`. The spec getter is `untrack`ed so reactive opts
(`$workspaceStore` etc.) are still captured-once — current `use()`
semantics unchanged.
- `UserDraftHandle.release()` and the `manualRelease` option are gone.
Component teardown is handled by a single internal `onDestroy` that
releases every entry `useMany` acquired.
ResourceEditor + VariableEditor migrated:
- Replaced `Record<ws, Handle>` + manual `ensureHandle`/`release` with
a `workspaceSpecs: $state<Array<{ws, defaultValue}>>` plus a
derived `Record<ws, Handle>` that pairs each ws with its parallel
handle from `useMany`. `ensureHandle(ws)` is now just a push to
the specs array; `VariableEditor.reset()` clears it. The reconcile
loop handles acquisition/release end-to-end.
Tests:
- Dropped the `manualRelease`/`release` test; the option no longer
exists.
- Added a `useMany` test asserting per-spec entries, isolated
workspace-scoped localStorage keys, and a single onDestroy
registration covering every acquired entry.
Implementation note: I tried wrapping `useLocalStorageValue` in
`$effect.root` to give the entry's `$state`/`$effect` an independent
scope (in case `useMany`'s reconcile effect tore down nested effects
across cycles). But `$effect.root`'s callback wasn't running
synchronously in the test runtime (vitest + svelte-vite plugin), and
the original `use()` implementation called `useLocalStorageValue`
directly without issue. Reverted to the direct call; the
nested-scope concern stays theoretical.
* fix(frontend): isolate UserDraft entries via $effect.root
The previous commit landed `useMany` calling `useLocalStorageValue`
directly. That works for the `use()` 1-spec wrapper (whose getter is
untracked, so the reconcile `$effect` never re-runs), but for dynamic
specs (ResourceEditor / VariableEditor) it leaks the persist `$effect`
into the reconcile `$effect`'s scope — meaning the second spec change
would destroy the first entry's deep-mutation persist loop.
Wrap the `useLocalStorageValue` creation in `$effect.root` so the
entry's reactivity lives in its own scope. Stash the returned
disposer on the entry and invoke it when the refcount hits 0.
The vitest runtime's `$effect.root` returns its disposer but never
runs the callback (a test-env quirk, not a production behaviour).
Kept a documented fallback that calls `useLocalStorageValue` directly
when the callback doesn't populate `stateRef`. In tests that path
parents the persist `$effect` to the test scope and lives long
enough; in production `$effect.root` runs the callback synchronously
per the Svelte 5 spec and the fallback is unreachable.
* chore(frontend): drop leftover console.log in setDraftConfig
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(frontend): wire ?nodraft=true to actually skip the local autosave on /edit
The flows/apps/apps_raw `/edit` routes had a `?nodraft=true` handler
that just stripped the param from the URL via `afterNavigate` —
nothing behind it. The original pre-PR semantics (and what every
caller assumes) was "skip the localStorage autosave on this load."
Mirror the synchronous wipe pattern already in /add: when nodraft is
present, call `UserDraft.remove(kind, path)` and strip the flag from
the URL via `window.history.replaceState`, before the UserDraft handle
is created. The handle then reads an empty entry and the editor opens
on the backend version. A plain reload (no nodraft) restores the
autosave normally.
Removed the redundant `afterNavigate` blocks. Dropped the now-unused
`afterNavigate` import in all three; apps/edit still imports
`replaceState` (used downstream), so only that name stayed.
* feat(frontend): GC UserDraft entries older than 30 days
Without a sweep, a heavy user accumulates one localStorage entry per
(workspace, kind, path) they ever touched. The pre-PR single-key
autosave self-capped at one entry per editor; this one needs an
explicit GC pass.
Mechanism:
- Stamp every persist with `lastWrittenAt: Date.now()`. Added at four
sites: `useLocalStorageValue`'s new `transformBeforePersist`
option (covers both setter and deep-mutation persists),
`UserDraft.save`'s no-handle fallback, `persistDirect` (force-meta
writes), and the legacy migration. Done at persist time, not in
`wrap()`, so deep mutations bump the clock too — `wrap()` runs only
on `.draft =` assignments, which would leave the timestamp stale for
bind-mutated editor sessions.
- `gcUserDrafts(maxAgeMs = 30d)` walks every `userdraft/w/...` key,
removes the ones older than the cutoff. Entries written before this
field existed (pre-PR or pre-this-commit) get backfilled with the
current time on first sweep so a 30-day clock starts fresh; the
alternative — sweeping on sight — would wipe work that the legacy
migration just rescued.
- Wired into the logged-in layout: runs once on mount and every 30 min
via `setInterval` (cleaned up in the effect's return).
Tests use `vi.setSystemTime` to drive the clock; assertions on the
stored payload now go through a `storedShape` helper that strips
`lastWrittenAt` before string-comparing, so the existing
`expect(...).toBe(wrapped(...))` style still reads cleanly. New tests
cover the sweep, the backfill behaviour, the default 30d window, and
a custom `maxAgeMs`.
* fix(frontend): break useMany reconcile feedback loop
The reconcile effect read `handles.length` / `handles[i]` for the
"unchanged?" early-exit optimisation and then `handles.splice(...)`
to publish the new array. Reading `handles` inside the effect
registered it as a dependency; the subsequent splice re-fired the
effect; ad infinitum (Svelte threw
`effect_update_depth_exceeded`).
Wrap the comparison reads in `untrack` so the effect's only
tracked dependency stays `getSpecs()`. The splice still fires the
downstream readers of `handles` (the whole point of `useMany`'s
reactivity); it just doesn't re-enter its own producer.
* fix(frontend): untrack the splice's own .length read in useMany reconcile
The previous fix wrapped only the comparison reads in `untrack`, but
`handles.splice(0, handles.length, ...next)` still reads `.length`
under the effect's tracking scope — same feedback loop, same
`effect_update_depth_exceeded`.
Move the whole "compare + splice" block inside `untrack`. The
downstream notification on splice still fires (untrack suppresses
dependency subscriptions on the producer side, not write
notifications), so consumers of `handles` still re-render.
* nit
* fix(frontend): drop in-memory handle before reloading after DB-draft discard
When the "Script/flow loaded from latest saved draft" toast's
"Reset to deployed" action ran, it:
1. Deleted the DB draft via DraftService.deleteDraft.
2. Called UserDraft.remove (clears localStorage only).
3. Called goto + loadScript / loadFlow.
But the handle's in-memory state still held the now-deleted DB draft
and its meta (remoteDraftRev pointing at the gone draft's created_at).
On the reload, the editor's loadScript/loadFlow saw `localDraft !=
undefined` and ran the staleness check, which compared
`meta.remoteDraftRev = <old timestamp>` against
`currentDraftRev = undefined`. Verdict: "version" stale → spurious
"A newer version was deployed on the server" modal, even though
nothing on the server actually moved. The editor visibly froze
behind the modal because the in-memory state wasn't refreshed.
Drop the in-memory state with `handle.setDraftAndMeta(undefined, {})`
before the reload — same fix already applied to the
"Restored from local storage > Reset to deployed" toast action.
apps/edit and apps_raw/edit's "discard draft" actions don't call
DraftService.deleteDraft (they just swap the in-memory view to the
deployed branch), so they don't hit this codepath.
* fix(frontend): drop in-memory handle in DiffDrawer restoreDraft/restoreDeployed
Same UserDraft.remove-without-clearing-in-memory bug as the previous
two commits, this time in the DiffDrawer's "Restore to draft" /
"Restore to deployed" buttons on all four /edit routes. The handler
deletes the DB draft (in the deployed case), wipes the localStorage
entry, navigates, and reloads — but the route's UserDraft handle
still holds the old draft + meta in memory, so the reload's
staleness check compares the stale meta against the freshly fetched
backend and surfaces a spurious "newer version was deployed" modal.
- scripts/edit, flows/edit, apps_raw/edit: route-level handle —
`handle.setDraftAndMeta(undefined, {})` before the reload.
- apps/edit: the handle lives in the AppEditor child, so force a
remount by setting `app = undefined; redraw++` before goto/loadApp
(matches the existing pattern from the toast's onResetToDeployed).
* fix(frontend): legacy app migration matches actual stored shape
Legacy AppEditor wrote `encodeState($appStore)` — the inner App value
(grid/fullscreen/theme/unusedInlineScripts/hiddenInlineScripts), not the
wrapping AppWithLastVersion. The plausibility check was matching the
wrapping fields, so real legacy app entries were filtered out and never
migrated to the new userdraft/w/{ws}/app/{path} keys.
* fix(frontend): untrack meta-preservation reads in UserDraft setters
`set draft`, `setMeta`, `UserDraft.save`, and `UserDraft.saveMeta` all
read `state.val` before writing it (to preserve existing rev metadata).
When called from inside a `$effect` — as AppEditor does to mirror its
reactive `$state` into the handle — the read subscribes the effect to
the entry's `$state` cell that the write then mutates, producing an
`effect_update_depth_exceeded` loop. Wrap the reads in `untrack` so
mirrors don't self-trigger.
* fix(frontend): apps detect drift + restore on /apps/add reload
Two related issues in the app editor's UserDraft wiring:
1. Drift wasn't detected on first deploy/draft after starting an
autosave. The route only backfilled meta on a reload that found a
local diff — so the first external change after editing slipped
through with empty `previousMeta`. AppEditor now receives the
load-time revs as `initialRevs` and seeds them into the handle's
meta on the first mirror, capturing the rev at autosave-creation
time.
2. /apps/add didn't restore from LS on plain reload. The route
always initialised `value` to `emptyApp()` and the AppEditor's
`stateApp` captured the prop unconditionally, so the LS autosave
was shadowed. `stateApp` now falls back to `appDraftHandle.draft`
when present; the template/hub/import branches explicitly
`UserDraft.remove('app', '')` to keep "start fresh from this
content" semantics.
Also work around `useLocalStorageValue`'s `saveInitialValue: false`
skip slot — in the mirror pattern the slot survived past mount and
swallowed the user's first edit. Consume it up-front with a
wipe-then-restore pair so subsequent edits persist normally.
* feat(frontend): restored-from-local toast in resource/variable editors
Resource and variable editors silently loaded LS autosaves over the
backend value, leaving users with no signal that the form wasn't
reflecting deployed state. Both now fire the standard
`notifyRestoredFromLocal` toast (with a "Reset to deployed" action
that re-seeds the handle from the just-fetched backend) the first
time a lazy-fetch finds the local draft diverging from the remote.
* fix(frontend): add UserDraft.discard so "Reset to deployed" doesn't re-persist
The "Reset to deployed" toast action in resource/variable editors
called UserDraft.save with the backend value to repaint the form. That
left a duplicate-of-backend autosave in localStorage which would
silently restore on every subsequent reload, defeating the reset.
New UserDraft.discard(itemKind, path, fallback) clears LS AND resets
any live handle's in-memory state to the fallback, skipping the next
persist so the fallback doesn't round-trip back into storage. Backed
by a new `skipNextWriteOnce()` method on useLocalStorageValue's return.
* fix(frontend): use UserDraft.discard in apps reset flows
The apps editor route doesn't hold the UserDraft handle — AppEditor
(the child remounted by {#key redraw}) does. When a reset action ran
`UserDraft.remove` + `redraw++`, Svelte could mount the new AppEditor
before the old one's onDestroy released its handle, leaving the
entry's in-memory state.val populated with the stale autosave. The
new AppEditor would then re-acquire that entry and shadow the
just-emptied localStorage.
Switch every reset path (stale modal Load latest, restored-from-local
toast, DiffDrawer restoreDraft/restoreDeployed) to `UserDraft.discard`
so the in-memory cell is cleared synchronously alongside LS. Also
plumb `currentRevs` updates so the next mount's initialRevs reflects
the acked state.
* fix(frontend): /flows/add restores autosave on plain reload
`loadFlow()` initialised the local `flow` variable to `emptyFlow()`,
then passed it to `initFlow` which writes it to `flowStore.val` (=
`flowHandle.draft = flow`). On a bare /flows/add reload (no
template/hub/import/fork/urlHash) the assignment overwrote the
persisted autosave with the empty baseline. Seed `flow` from
`flowHandle.draft` instead, and keep `emptyFlow()` as the explicit
"start fresh" baseline for template/hub branches.
* nit rename
* fix(frontend): snapshot UserDraft proxy before structuredClone in resource save
`states[ws].draft` is now a Svelte $state proxy (it flows through
UserDraft's useLocalStorageValue cell). `structuredClone` can't clone a
proxy and threw "Failed to execute 'structuredClone' on 'Window'",
blocking resource saves. Snapshot to a plain object via
`$state.snapshot` before assigning the dirty baseline.
* fix(frontend): raw app deploy toast crash + harden Toast against bad type
RawAppEditorHeader's catch blocks called `sendUserToast(msg, e)`,
passing an Error as the `_type` arg. `classes[<Error>]` is undefined so
`color.descriptionClass` threw — and because the toast renders in the
root layout, it crashed the whole page on raw app deploy/create. Fixed
both call sites to the proper `(msg, true)` error form.
Also hardened Toast.svelte: coerce any non-AlertType `type` to 'error'
so a future miscall degrades to a plain error toast instead of taking
down the page.
* fix(frontend): /apps_raw/add restores autosave on plain reload
The route initialised files/runnables/data/summary to hardcoded
defaults, and the $effect mirror then wrote those defaults over the
persisted empty-path autosave. Seed the $state from
`draftHandle.draft` instead; import/template/hub branches
`UserDraft.remove('raw_app', '')` for explicit "start fresh"
semantics. Also consume useLocalStorageValue's saveInitialValue=false
skip slot (wipe-then-restore) so the user's first edit isn't dropped.
* feat(frontend): staleness modal in resource/variable editors
Resource/variable editors only showed the restored-from-local toast;
they never surfaced the staleness modal when the backend item moved on
since the local autosave was written. Wire LocalDraftStaleModal +
checkStaleness using the backend `edited_at` as `remoteRev` (these
items have no DB-draft concept). Meta is backfilled on reload for
legacy autosaves and seeded on the first real edit via a guarded
effect, so an external edit is detectable as drift. Per-workspace
detection; the modal is a singleton driven by `pendingStale`.
* feat(frontend): restored-from-local toast in standalone trigger editors
The schedule/postgres/http/kafka/websocket/email/sqs/nats/gcp/azure/
mqtt editors silently overlaid the local UserDraft autosave on top of
the backend config in `openEdit`, with no signal that the form wasn't
showing deployed state. Each now snapshots the just-loaded backend
config, then fires `notifyRestoredFromLocal` with a "Reset to
deployed" action that drops the LS entry and re-applies the snapshot.
* fix(frontend): trigger autosave no longer false-restores on plain open
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(frontend): live UserDraft handle for trigger editors
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(frontend): live UserDraft sync for raw app editors
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(frontend): extract useTriggerDraftSync composable
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs(frontend): trim rot-prone comments in UserDraft
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* in /script, put code state in URL
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
The light Monaco theme ('myTheme') did not define editorCursor.foreground,
causing the cursor to be invisible on white backgrounds. The dark theme
('nord') already sets this explicitly.
Fixes#8876
* [ee] fix(s3): sandbox stored XSS via download response headers
Reported chain: a workspace user uploads xss.html via
apps_u/upload_s3_file with content_type=text/html&content_disposition=inline;
when an admin clicks the resulting download URL the browser renders
the attacker page in Windmill's origin and can escalate via the
SameSite=Lax session cookie.
Fix on the download side only — leaves upload semantics unchanged so
existing integrations are not affected:
- download_s3_file_internal (used by apps_u/download_s3_file and
job_helpers/download_s3_file) emits X-Content-Type-Options: nosniff
and Content-Security-Policy: sandbox on every response (EE).
- The HTTP static-asset trigger emits the same headers on single-file
responses. Static-website responses keep their existing semantics
(CSP sandbox would break a legitimate static site); restricting
write access to those buckets remains the documented mitigation.
Sandbox loads any HTML/SVG into an opaque origin so the page cannot
reach the viewer's cookie or /api/*. Images, PDFs, and fetch-driven
previews are unaffected (browsers ignore CSP for <img>/<embed> and
for fetch responses).
Companion: windmill-ee-private fix/s3-content-type-xss.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to daffe7bb81cfcaca666c61de1ee838a44d60ebc2
This commit updates the EE repository reference after PR #585 was merged in windmill-ee-private.
Previous ee-repo-ref: e889b86ee1c68c2f7cf9b07ec4b8ba6e6b66a169
New ee-repo-ref: daffe7bb81cfcaca666c61de1ee838a44d60ebc2
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>
* stash
* ui nits
* Fix contenteditable feedback look (duplicate typing)
* fix right icon wrong position with placeholder
* user editor in Path editor takes correct width
* nits
* nit
* chore: remove assets-operator changes (moved to separate PR)
These files were mistakenly included in this PR and belong in a dedicated PR
("Allow assets page to operators").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: remove sidebar assets-operator change (moved to separate PR)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix disabled
* border nit
* Fix disabled styling
* Apply suggestion from @cubic-dev-ai[bot]
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* nit
* Update frontend/src/lib/components/text_input/TextInput.svelte
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* Fix disabled tabindex and aria-disabled on contenteditable Select
The useContentEditable branch had an unconditional tabindex="0", keeping
a disabled Select in the tab order, and was missing aria-disabled.
Mirror the TextInput div branch.
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
* fix: drop obsolete hideFullPath prop from EditorHeader Path usage
* invalidate autocomplete paths on deploy
* nit pixel
* use Badge in auto complete
* nit prevent default
* fix(autocomplete): don't let stale fetch clobber forced refresh
A non-forced fetchWorkspacePaths() that started before invalidateWorkspacePaths()
could still resolve afterward, overwrite the cache, and clear forceNextFetch —
making the post-deploy refresh a no-op. Only write back from the promise that
is still the current pending one, and only clear the force flag when the
completing fetch was itself forced.
* refactor(path): drop unreachable 'group' branch in owner-kind setter
The Select only offers user/folder, so the 'group' branch was dead. Leave a
short note pointing at validateName which still accepts 'group' for
forward-compat.
* fix(path): respect disableEditing on owner-kind selector
Other path-editor controls disable on (disabled || disableEditing); the
owner-kind Select only checked `disabled`, so read-only users (trigger
editors with !can_write) could still toggle User/Folder and mutate the
bound path. Reuse the existing nameDisabled flag.
* Revert "fix(autocomplete): don't let stale fetch clobber forced refresh"
This reverts commit 6649975714.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
* feat(nsjail): make tmpfs size configurable via instance setting
Adds a new `nsjail_tmpfs_size_mb` instance setting that overrides the
size of the `/tmp` tmpfs mount inside the nsjail sandbox across all
languages. When unset, the existing per-language defaults (500MB or
800MB) continue to apply, so no behavior change for existing
deployments.
The setting is exposed under Settings → Jobs and is read at job
execution time, so changes take effect on the next job without a
restart.
Fixes WIN-1963
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(nsjail): unify default tmpfs size to 800MB
Previously each executor passed its own per-language default (500MB or
800MB) to resolve_nsjail_tmpfs_size. Unify on a single
DEFAULT_NSJAIL_TMPFS_SIZE_BYTES constant (800MB) so the placeholder
behavior is consistent across languages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(nsjail): resolve tmpfs size outside ruby download closure
The download.ruby config render runs inside a sync closure passed to
par_install_language_dependencies_seq, so `.await` on
resolve_nsjail_tmpfs_size() was a compile error under the `ruby`
feature. Resolve the size once before the closure and capture the
string instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(nsjail): rename resolver to *_bytes and clarify fallback
Addresses CI review feedback:
- Rename `resolve_nsjail_tmpfs_size` to `resolve_nsjail_tmpfs_size_bytes`
so the returned unit is unambiguous at the call site (cubic P2).
- Fix the `NSJAIL_TMPFS_SIZE_MB` doc comment that still said "per-language
default" — there is no per-language fallback anymore, all unset
values resolve to the unified 800MB `DEFAULT_NSJAIL_TMPFS_SIZE_BYTES`
(codex/pi P2).
- Expand the resolver doc to call out that `Some(0)` and negative values
also fall back, since the match arm is `Some(mb) if mb > 0`.
No behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(debug): show ghost breakpoint and tooltip on gutter hover
* fix(debug): show ghost breakpoint only on glyph margin to match click handler
* revert(debug): show ghost across entire gutter, not only glyph margin
* refactor(debug): use MouseTargetType enum, short-circuit hover decoration
* feat(editors): responsive top-bars + script test-pane pixel-min + flow graph overlay
Editor top bars now collapse on narrow widths (measured via container
clientWidth, not viewport — they live inside drawers / session panes
where the viewport stays wide):
- FlowBuilder: Diff + Save draft fold into the ellipsis menu when
the top bar narrows below 720px (Save draft keeps its ⌘S / Ctrl+S
shortcut indicator). Test-flow button moves out of the top bar
and into a graph-pane overlay matching the dev page; the overlay
position flips from top-2 right-2 to top-14 left-1/2 when the
graph pane itself is narrower than 800px. FlowEditor exposes a
graphOverlay snippet prop for that.
- ScriptBuilder: Settings + Draft labels collapse to icon-only;
a new DropdownV2 ellipsis surfaces Tag / Settings / Save draft
when even icons don't fit. The ellipsis itself uses variant=subtle.
- AppEditorHeader / RawAppEditorHeader: fullscreen / dark-mode /
breakpoint toggle group + Debug-runs / Jobs buttons hide; Save
draft moves into the Deploy dropdown.
- EditorBar: a "Helpers" DropdownV2 collapse for Context var /
Variable / S3 / Resource / Git repo / Resource type / Database /
Ducklake / Data table / Reset when the bar narrows below 800px
(EDITOR_BAR_HELPERS_COMPACT_THRESHOLD). Above that, the existing
icon-only mode (1420px threshold) still applies.
- ScriptEditor's test pane gets a pixel-based minimum width (400px)
derived from the splitpane's clientWidth. The Pane uses Svelte 5
function-binding so the splitter writes to a raw $state while the
splitpane reads the clamped derived value — no $effect, no
release-time bounce, drag stops at the boundary. Cap raised to
80% so the test pane can take most of the editor on very narrow
layouts while leaving a sliver of code visible.
- VS Code button on ScriptEditor: collapses to icon-only below the
EDITOR_BAR_WIDTH_THRESHOLD (1420px) instead of being hidden
entirely by viewport `lg:` breakpoint; hidden completely when the
editor is rendered inside a session pane.
- AI wand button on ScriptEditor + RawAppEditorHeader: hidden inside
a session pane (detected via `getContext('aiChatManager')`) — the
session owns its own AI chat.
- DeployButton: drops the unused `newFlow` gate (callers updated).
- FlowDiffViewer / FlowGraphDiffViewer: inlineDiff prop forwarding
+ onHeight callback on FlowGraphV2 so diff viewers can equalize
side-by-side graph heights.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ui: unify Debug / wand / test-toggle button sizes + HideButton defaults
Two small consistency passes on shared button components:
- ScriptEditor's Debug, AI wand and Test-panel-toggle buttons all
use unifiedSize="sm" so they line up in the toolbar; Test toggle
switches from custom marine btnClasses to variant="accent-secondary".
HideButton gains a passthrough unifiedSize prop so the wand and
test toggle can match Debug without overriding btnClasses.
- HideButton's own defaults shift to variant="subtle" + sm
unifiedSize, dropping the legacy color="light" / variant="contained"
+ tailwind-merge background overlay; the selected (hidden) state
is now a tinted wrapper div instead of overriding btnClasses.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ui(script): editor toolbar polish
Small consistency tweaks on the script editor's top-right overlay:
- Lowercase "test" / "Exit debug" panel labels.
- `bg-surface` on the overlay container so the absolute-positioned
buttons read as a single panel over the graph rather than disjoint
pills.
- Debug button picks up `destructive={debugMode}` so the active
state reads as "you're in debug mode" instead of accent.
- Console and "Delegating to git repo" buttons drop the custom
`btnClasses` border-on-surface treatment and switch from
`size="xs"` to `unifiedSize="sm"` so they match the other buttons
in the cluster.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(raw-app): collapsible file sidebar, default-collapsed in session preview
The raw-app editor's left sidebar (file tree, runnables, history) ate a
lot of horizontal space — fine in the standalone editor, painful in the
session preview pane where the chat is already taking half the screen.
Add a small collapse / expand toggle. Persist the user's preference in
localStorage so it sticks across opens.
Two independent localStorage keys via the new `sidebarStorageKey` prop:
- standalone editor: `raw-app-sidebar-collapsed` (default expanded)
- session preview: `raw-app-sidebar-collapsed-preview` (default collapsed)
Otherwise the two contexts would race for the same key — whichever
opens first would dictate the other's default. Splitting the keys lets
each have its own remembered preference.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fixup(editors): use untyped getContext for AI-chat-manager session detection
The cherry-picks landed `getContext<AIChatManager>('aiChatManager')` to
hide per-editor AI/VSCode buttons when rendered inside a session pane.
The `AIChatManager` class is exported only on the sessions branch (used
for typing session-provided manager overrides). On `main` the manager
file exports only the singleton instance, so importing the class fails
the type-check.
The session-pane detection just needs a truthy/falsy probe — drop the
type parameter and the class import. `inSessionPane` ends up as
`getContext('aiChatManager')` (returns `unknown`, coerced to boolean
via `!!`). Same runtime behaviour, no class-export dependency.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ui(editors): flow test in top bar; ellipsis folds draft/jobs/tutorials
* refactor(app-editor): drop dead AppEditorTutorial button path
* ui(editors): wire compactHelpers in flow-step + raw-app inline editors
* ui(raw-app): sidebar Cmd/Ctrl+B toggle + uppercase section titles
* ui(editors): keep Diff/Settings inline as icon-only when narrow
* fix(editors): address review nits on test-pane/Helpers/thresholds
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(chat): waiting-for-user indicator and arrow polish
- Show "Waiting for your input" (text-accent + flipping Hourglass) instead
of the typing dots when the latest tool is staged for confirmation
(Run/Cancel) or has an active askUserQuestion. The dots imply the AI
is working, which is misleading when the loop is paused on the user.
- Scroll-to-latest arrow:
- Move up to bottom-12 when the flow Accept/Reject row is visible so
they no longer overlap.
- Wrap in a solid bg-surface + shadow + border badge so the icon
doesn't bleed into messages behind it.
- Bump unifiedSize xs → sm for a slightly larger target.
- Hourglass uses a custom CSS keyframe (:global so the rule reaches the
Lucide SVG root) with 4 s period and cubic-bezier(0.65, 0, 0.35, 1)
easing — feels like flipping the hourglass rather than spinning.
* fix(chat): raise waiting indicator above accept/reject row
* fix(chat): solid background behind reject all button
* feat(chat): @ picker in controls row, badges above input, polish
* feat(cli): add datatable list and run commands
* feat(cli): render datatable query results as a table
* feat(cli): add ducklake list/run, scope --name to run subcommand
* [ee] fix(saml): preserve deep links from /a/[...path] across SAML round-trip
Fixes WIN-1962.
PR #9225 only covered users who pass through /user/login on their way to
the IdP — that's where `redirectSaml()` runs and where the deep link gets
stuffed into `RelayState`. The reported flow doesn't go through that
page: it hits `/a/[...path]` (the public-app custom-path route, outside
the `(logged)` layout) where `PublicApp.svelte` renders its own `<Login>`
and was passing `page.url.toString()` as `rd` — the full URL.
Three problems compounded:
1. `redirectSaml()` only set `RelayState` when `rd.startsWith('/')`,
so a full URL silently fell through and the deep link was lost.
The IdP echoed back the SP-library default (BASE_URL), which the
ACS validator correctly rejected as a potential open-redirect.
2. `persistRd()` stored the full URL in `localStorage.rd`. On the
fallback landing at `/user/login`, the post-login redirect saw
an `http://...` value, hit the cross-origin branch, and bounced
to `/` — which from a logged-in but workspace-less state shows
the "Loading user…" modal forever (bug 2).
3. The EE `safe_relay_state_redirect` validator rejected any full
URL, including same-origin ones, so even IdPs that prepend the
origin or that pass a configured absolute deep link via
IdP-initiated SSO got dropped on the floor.
The fix is a single concept applied at every layer: reduce a redirect
target to a safe same-origin relative path, or refuse it.
Frontend:
- `logoutRedirect.ts`: new `toSameOriginRelativePath(rd)` helper that
accepts both `/foo` and `https://current-origin/foo`, with the same
open-redirect guards as the backend (length cap, control chars, no
protocol-relative or back-slash tricks). Returns `null` for
cross-origin or malformed input.
- `PublicApp.svelte`: pass `pathname + search + hash` to `<Login>`
instead of the full URL — this alone fixes the happy path.
- `Login.svelte`: `redirectSaml()`, `persistRd()`, and `redirectUser()`
all route through the helper, so full URLs from `/a/[...path]` are
reduced before being put in `RelayState`/`localStorage`/`goto()`.
- `/user/login/+page.svelte`: the same reduction is applied to the
resolved `rd` so any stale full-URL value in `localStorage.rd` still
navigates to the intended page instead of falling into the
cross-origin branch.
Backend (EE companion: windmill-ee-private#TBD):
- `safe_relay_state_redirect` now reduces a `RelayState` whose origin
matches `BASE_URL` to its path before applying the same-origin path
safety rules. Bare BASE_URL with no path still falls back to
`/user/login` (no useful deep link to honor).
- New `same_origin_relative_path` helper + expanded unit tests.
Test plan:
- [x] Frontend: `vitest run src/lib/logoutRedirect.test.ts` — 9 passed
- [x] Backend: `cargo test -p windmill-api ... saml_ee::tests` — 3 passed
(`honors_same_origin_relative_path`, `reduces_same_origin_full_url_to_path`,
`falls_back_on_open_redirect_attempts`)
- [ ] Manual e2e (needs configured SAML IdP — not on local CE):
- Unauthenticated visit to `/a/<path>` → click SSO → SAML → land on
`/a/<path>` (RelayState now carries the relative path).
- IdP that echoes BASE_URL as default → ACS still falls back to
`/user/login` (no useful path to honor), but the page no longer
hangs: the stale full-URL `localStorage.rd` is reduced to its path
and the post-login redirect navigates to it.
- Tampered `RelayState` (`//evil.com`, `https://evil.com/x`) → ACS
rejects, lands on `/user/login`.
* chore: update ee-repo-ref to 3489c243b0e5a8eb0dbc86e90917fbe72843573b
This commit updates the EE repository reference after PR #584 was merged in windmill-ee-private.
Previous ee-repo-ref: 635ff3eeb8e47bb84d5686942605f67f8f6224b4
New ee-repo-ref: 3489c243b0e5a8eb0dbc86e90917fbe72843573b
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: thread temp_script_refs into preview jobs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: resolve python preview relative imports from temp script refs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: use local relative imports in wmill script preview
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: use local relative imports in wmill flow preview
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add temp_script_refs to Preview and FlowPreview openapi schemas
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: pass temp_script_refs to bun lockfile gen for no-lock preview
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: route script preview through shared buildPreviewTempScriptRefs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: resolve local relative imports in wmill app dev inline scripts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address cubic review — bundle cache key, preview-mode gate, error masking
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* perf: skip dep-tree build when previewed script has no relative imports; narrow old-backend classifier
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address review issues (preview-only gate, bundle preview, app dev cwd)
Three P1s flagged in repeated codex/pi reviews on PR #9233:
- Gate _TEMP_SCRIPT_REFS extraction on JobKind::Preview (bun + python
executors) and propagation in worker_flow on JobKind::FlowPreview. job.args
includes caller-controlled request args, so honoring this key on deployed
runs would let a caller swap import resolution to local content uploaded
via /raw_temp.
- run_bundle_preview_script now injects temp_script_refs into PushArgs.extra,
mirroring run_preview_script — closes the silent data drop for the bundle
preview path.
- wmill app dev chdirs to the wmill.yaml root before buildPreviewTempScriptRefs
and restores after, so the `cd <app>__raw_app && wmill app dev` invocation
(cwd is the raw_app folder, no app_folder arg) still walks sibling workspace
scripts like f/lib.ts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(ee): bump ee-repo-ref to 5b347d6 (handle_python_deps arity fix)
Picks up the EE arity fix so cargo_test + check_ee_full compile cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(ee): bump ee-repo-ref to 52e273d (agent-workers bundle path arity fix)
Picks up windmill-ee-private 52e273d which adds the missing &None arg to
compute_bundle_local_and_remote_path in windmill-api-agent-workers/src/ee.rs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(ee): bump ee-repo-ref to 2d6ffd3 (EE main merged in)
Previous bump pinned an older EE commit, missing the audit-log object-store
export module (EE PR #579, commit ec3cd35) and other EE main updates. The
CE backend's `crate::ee_oss::anchor_audit_logs_s3_checkpoint_env_var` and
`export_audit_logs_to_object_store` references need the new EE definitions.
Merged origin/main into the EE branch and pinned the merge commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to b0c87b1272c25dca4aa9148c87fb024a9d9ef322
This commit updates the EE repository reference after PR #583 was merged in windmill-ee-private.
Previous ee-repo-ref: 2d6ffd32c99bd93e79cf78675cb89499a81b17e1
New ee-repo-ref: b0c87b1272c25dca4aa9148c87fb024a9d9ef322
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>
* feat(ai-chat): link workspace paths and show tool item references
Detect Windmill paths (u/..., f/...) in assistant messages and render
them as clickable pills with the right icon, resolved against a per-
workspace cache. Tool execution headers now list the script/flow/app
paths referenced in tool parameters as external links.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ai-chat): linkify inline-code paths, refine pill styling
- Inline-code spans whose value is exactly a Windmill path now render
as a link pill (paths inside larger inline code or fenced blocks
stay as code).
- Tool-header chips moved to their own row to avoid overflow clipping
when the title wraps.
- Borderless pills, no default background (hover only), kind icons
use the home-page palette (script blue, flow teal, app orange),
and the external-link indicator only appears on hover.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ai-chat): linkify variables/resources/triggers + inline drawer
- Workspace item registry now also lists variables, resources, schedules,
and all 10 trigger kinds; resource wins over variable on path collisions
(Windmill auto-creates a companion variable for every resource).
- Pill icons delegated to the canonical RowIcon component so each kind
matches the home-page styling (script blue, flow teal, app orange,
resource boxes, schedule calendar, etc.).
- Pill href includes the hash fragment each list page already consumes
(#/resource/<path>, #<path> for variables/schedules/triggers), so
opening the link puts the user on the list page with the matching
editor drawer already open.
- For variable and resource pills, a hover-revealed side-panel button
opens (or toggles closed) the editor drawer inline next to the chat,
without navigating away. VariableEditor and ResourceEditorDrawer gain
a closeDrawer() export and forward their close event so the host can
drive toggling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: simplify ai chat workspace item links
* refactor: keep ai chat path linkification only
* perf: avoid eager ai chat path cache loads
* refactor: simplify ai chat path linking
* feat: open ai chat path links in drawers
* refactor: homogenize workspace item kinds
* fix: toggle ai chat item drawer
* refactor: trim ai chat path cache
* fix: cancel ai chat drawer reopen
---------
Co-authored-by: Guilhem Lemouel <guilhemlemouel@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Two FlowProgressBar bugs:
1. stop_after_if (without 'label as skipped') ends the flow with
step < modules.length, leaving the bar at <100% with a spinner.
2. failure_module execution drives step past modules.length, so the bar
overflows past 100% and never reflects the error.
The fix clamps progress to the failed module when the error handler
runs, and forces 100% Done when the flow completed successfully but
stopped early.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] fix(autoscaling): only divide native_mode pings by NUM_WORKERS
#9020 / EE #548 changed worker counting to COUNT(DISTINCT worker_instance) to
fix native-mode over-counting (NUM_WORKERS=8 pings per pod). That collapsed
custom worker groups that share a hostname across multiple worker processes
to a count of 1, breaking their autoscaling.
EE fix uses the per-ping native_mode flag: divide native rows by 8 (CEIL),
count non-native rows as-is.
Companion EE PR: windmill-labs/windmill-ee-private#fix-autoscaling-custom-worker-group-count
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to abeb405de36a3fe23382f19e762268f87b6679be
This commit updates the EE repository reference after PR #582 was merged in windmill-ee-private.
Previous ee-repo-ref: 135db676843346ed6e3015232161a49c3ce01db5
New ee-repo-ref: abeb405de36a3fe23382f19e762268f87b6679be
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>
Defines a top-level oneshot.systemPrompt block so webmux oneshot runs
get explicit guidance: no interactive user, take the task through PR,
and only mark a PR ready-for-review when highly confident (otherwise
draft).
* feat(chat): visual redesign — input, streaming indicator, scroll polish
Visual refresh of the AI chat surface used in both the global right-side
panel (Cmd+L) and inline editor panels. No new features, no system-prompt
or tool changes, no sessions code.
Input redesign
- Default textarea to `rows={1}` and autosize as the user types.
- Drop the separate Send button row in favour of a single
`<Button variant="subtle" iconOnly>` overlaid bottom-right of the
textarea — `ArrowUp` when idle (disabled until text is typed),
`Square` when loading (cancels via `aiChatManager.cancel()`).
- Padding `!pl-3 !pr-10 !py-2` keeps text clear of the floating button.
- Top spacing `mt-1` on the outer wrapper restores breathing room
above the input (lost when the old @-button row was removed).
- Context chip row renders only when something is selected.
- `ContextTextarea` `min-height: 2.25rem` so the empty textarea
collapses to a tight single line.
Streaming indicator
- Replace the old floating "Stop" button with a sticky-bottom badge
showing three animated typing dots and a formatted wall-clock
(`Xs`, `Xm Ys`, `Xh Ym`) — driven by `aiChatManager.loading`.
- CSS keyframes `chat-typing` with staggered animation-delay for the
wave effect.
Scroll behaviour
- Replace `onwheel`-based stick-to-bottom detection with `onscroll`
position check (8px threshold). Auto-scroll re-engages when the
user scrolls back near the tail.
- Smooth scroll → `behavior: 'auto'` so token-append doesn't race
the animation.
- New `enableAutomaticScroll` method on `AIChatManager`, complement to
the existing `disableAutomaticScroll`.
- Floating "scroll to latest" arrow (`ArrowDown` design-system Button,
`transition:fade`, `unifiedSize="xs"`, `iconOnly`) appears once the
user scrolls >200px above the tail; click re-enables auto-scroll
and jumps to bottom. Centered horizontally over the scroll viewport.
Message rendering
- Assistant markdown tuned: `prose-headings:font-medium`, h1 `text-sm`,
h2+ `text-xs`, plus `prose-p:text-xs prose-li:text-xs
prose-code:text-xs prose-pre:text-xs`. Stops AI replies blasting
oversized titles.
- Fenced code blocks shrink to `!text-xs` on the `not-prose` wrapper
so fenced code matches inline code at 12px.
- User-message wrapper switches to symmetric spacing (`mt-4 mb-6`)
with a new `isLast` prop that adds `!mb-12` to the latest message
— breathing room between the last bubble and the input without
affecting siblings.
Layout / padding
- Wide-layout messages tightened to `px-7` (was `px-8`); input outer
to `px-6`. The input box sits a touch left of the message text;
textarea's own `!pl-3` brings the typed text back into alignment
with the messages above.
Other
- `AIChatManager` class is now exported (was private). Allows callers
to type a `getContext<AIChatManager>('aiChatManager')` provider
override. No behaviour change for the global singleton.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(chat): restore @ picker, extract typing indicator and shared helpers
* feat(chat): cap non-wide chat at max-w-2xl, add side padding, drop input top border
* feat(chat): esc cancels active generation, tone down snapshot row
* fix(chat): only draw tool-content fade when content actually overflows
* style(chat): tighten non-wide side padding (px-4/px-3 -> px-3/px-2)
* fix(chat): inline ⌘K shows dots + stop button, swallow programmatic scroll events
* fix(chat): keep scroll-to-latest fresh during cooldown; ResizeObserver for tool-content fade
* fix(chat): contain wide content - propagate showFade, table scroll, bubble + inline code wrapping
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(snowflake): derive public key from private key when omitted (WIN-1959)
Snowflake key-pair auth needs a SHA256 fingerprint of the public key for
the JWT iss claim, but the public key is mathematically derivable from
the RSA private key. Other tools (e.g. Power BI) only require the
private key, so requiring users to supply both is redundant. When
public_key is missing, fall back to deriving it from private_key (PKCS#8
or PKCS#1 PEM) instead of erroring out.
Fixes WIN-1959
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(snowflake): treat empty public_key/private_key as missing
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- guard-main-branch.sh: exit 2 on block (was advisory echo), and block
force-push to main from any branch (--force, -f, --force-with-lease, +ref)
- settings.json: gate claude.ai MCP connectors (Stripe, Gmail, Calendar,
Drive, Slack, Linear) behind permissions.ask
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(vault): add optional KV secret path prefix setting (WIN-1960)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 0189ba6504fd70eb4929e4881d624d48efd14aee
This commit updates the EE repository reference after PR #581 was merged in windmill-ee-private.
Previous ee-repo-ref: e32e8d6483550c67897e09b6f900dff1034bdae8
New ee-repo-ref: 0189ba6504fd70eb4929e4881d624d48efd14aee
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: enforce auth guards on app component preview execution
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: guard previewed runnable path and worker tag in app preview
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: validate app_script id ownership and keep root push isolation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: scope app preview guards to operator check + referenced runnables
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: require jobs:run scope and tag check on app preview (apps:run escalation)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 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>
* [ee] feat(indexer): observability for unavailable search index
A user hit `Not found: There is no index reader to search from` when
searching service logs and could not tell whether it was a config
error or a bug, and asked for visibility into the indexer status
(WIN-1956).
Backend (EE companion PR):
- Replace the opaque error with an actionable message explaining the
likely causes (indexer disabled, still starting, or blocked
acquiring the indexer lock) and pointing to the status panel.
- Add a coarse `state` (running | stale | never_started) to
`/indexer/status`, derived from the lock row, distinguishing a
never-configured indexer from a stale/blocked one.
Frontend:
- Instance Settings > Indexer now shows Running / Stale / Not started
with a tooltip explaining what to check for each.
- Service logs search now catches failures and shows an inline,
actionable Alert instead of an unhandled rejection.
Fixes WIN-1956
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 017d36418a65ce5c840c502e3174df0c393612ba
This commit updates the EE repository reference after PR #580 was merged in windmill-ee-private.
Previous ee-repo-ref: 18b7e1b30a1ff582c4a072580bbb8aec34e22cdc
New ee-repo-ref: 017d36418a65ce5c840c502e3174df0c393612ba
Automated by sync-ee-ref workflow.
* fix(indexer): address review nits
- IndexerMemorySettings: older backends without `state` reporting
`is_alive: false` now show "Stopped" (red) again instead of
falling through to "Unknown" (codex/cubic P2).
- ServiceLogsInner: clear stale logs/counts on a failed search so the
error isn't shown alongside results from a previous query (codex P2).
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>
* fix(frontend): open customer portal in popup synchronously to bypass Safari blocker
Safari blocks window.open() called after an await because it loses the
user-gesture context. Open a blank tab synchronously on click, then
assign location.href once the portal URL resolves.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(backend): wire dev_override feature flag in backend crate
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: export audit logs to dedicated object store folder
* fix: gap-free audit export via snapshot-xmin gate and stable object keys
* test: add integration test for audit log object store exporter
* fix: cursor audit export on snapshot xmin to prevent id-leapfrog loss
* fix: protect audit s3 checkpoint from config sync and bound export interval
* fix: anchor audit s3 checkpoint at enable time to not skip first-window rows
* fix: anchor first audit export at the enable transaction's xid
* fix: use epoch timestamp floor on first audit export run to not drop old backlog
* fix: anchor audit export at startup for env-var enable path
* fix: anchor audit export via enabling-txn snapshot xmin trigger
* fix: bound the bootstrap audit export to MAX_XID_INTERVAL per tick
* refactor: store audit export cursor in background_task_state, add status endpoint
* docs: align store_audit_logs_s3 setting text with the actual enable-boundary contract
* [ee] refactor: move audit s3 export core logic to EE, gate on Enterprise license
* chore: update ee-repo-ref to ec3cd353245e1cdf6a290528dbd7f2ac2498386c
This commit updates the EE repository reference after PR #579 was merged in windmill-ee-private.
Previous ee-repo-ref: 4ffc6d5f874e64d7dc4a147b4e73baa6c44867a5
New ee-repo-ref: ec3cd353245e1cdf6a290528dbd7f2ac2498386c
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(autoscaling): full-scale from below min_workers when backlog exceeds threshold
Bump EE ref to pull in the autoscaling fix: when active worker count is
below min_workers and a relevant tag's queue depth already exceeds
full_scale_jobs_waiting, scale straight to max_workers instead of slowly
ramping to min_workers first.
Companion EE PR: windmill-labs/windmill-ee-private#improve-pr-9209
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to b4d68e40430cf0300b5d37734d1505fd743f1059
This commit updates the EE repository reference after PR #578 was merged in windmill-ee-private.
Previous ee-repo-ref: 99810eb763703ef0f4b3311338e0e65f53544158
New ee-repo-ref: b4d68e40430cf0300b5d37734d1505fd743f1059
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: enable jemalloc background purge to prevent worker RSS growth
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: drop decay overrides, keep only jemalloc background_thread
The background thread is the actual fix; jemalloc's default decay
windows (dirty 10s, muzzy 0) are correct for months-long workers and
muzzy_decay_ms:5000 was more retentive than the default.
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: scope SimpleEditor webview paste to focused editor instance
* fix: scope webview clipboard paste to focused editor instance
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: bail on missing selection instead of pasting at document start
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: hide SimpleEditor paste sink input from a11y tree and tab order
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: advance postgres replication slot lsn via periodic standby status updates
* test: add e2e regression test for postgres replication slot lsn advancement
Drives sustained change traffic so the slot freeze reproduces deterministically (fails pre-fix at the 20s deadline, passes post-fix within ~10s). Also wires the postgres_trigger feature through windmill-test-utils and the integration-tests crate so the postgres trigger e2e tests are actually runnable.
* fix: honor SAML RelayState to redirect to deep link after SSO login
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref for SAML RelayState validator test
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to a3fefe85f5f2f52bb473fa47acc9efa8fd0b2206
This commit updates the EE repository reference after PR #577 was merged in windmill-ee-private.
Previous ee-repo-ref: 445a22536b1a6c342cde0baa6fbca9e25092f94b
New ee-repo-ref: a3fefe85f5f2f52bb473fa47acc9efa8fd0b2206
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: reject path traversal in MCP endpoint path parameters
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: narrow MCP path-param validator to structural escapes only
Codex review: rejecting whitespace/`:`/`@` regressed legitimate
Windmill paths (app paths with spaces, email-style usernames like
u/admin@windmill.dev/...). These are ordinary path-segment data in an
absolute URL and cannot redirect the request. Reject only structural
escapes: control chars, `\`, `%`, `?`, `#`, and `.`/`..`/empty segments.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the Docusign Authorization Code OAuth entry. Used by the
Docusign integration in the windmill-integrations hub (PR #128).
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat: show job status in favicon on the run page
* test: cover getJobStatusKind favicon status mapping
* chore: remove favicon unit tests
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: resolve absolute-path imports in monaco ts editor
* fix: dispose absolute-path extra libs on editor teardown and reset
* fix: skip late ata local-file callbacks after editor teardown
* fix: enforce jobs:run scope on job preview and inline endpoints
Preview/inline endpoints (run/preview, run/preview_bundle, run/preview_flow,
run/dynamic_select inline) execute arbitrary request-supplied code but only
checked folder/namespace read access, which is a no-op when path is null. A
token scoped to a specific script/flow could escape its scope and run any
code. Add a jobs:run scope check, matching other arbitrary-execution
endpoints. Advisory GHSA-vxc5-w28p-m9xw.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: scope-check dynamic_select flow branch and inline preview
Address CI review: the dynamic_select Deployed{Flow} branch ran a deployed
flow's dynamic-select code without any scope check (only the Script branch
delegated to a scope-checked handler), and run_inline_preview_script executed
request-supplied code with no in-handler scope check. Add jobs:run:flows:{path}
to the flow branch and jobs:run to inline preview; correct the misleading
comment. Expand regression tests (preview_flow case, assert success for the
broad-token case). Advisory GHSA-vxc5-w28p-m9xw.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: remove preview scope enforcement test after local validation
The regression test passed locally (3/3) and validated the fix end-to-end;
removed from the PR per maintainer preference. Advisory GHSA-vxc5-w28p-m9xw.
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 dev profile's split-debuginfo = "unpacked" is coerced to "packed" on
windows-msvc, so each test-binary link spawns the shared mspdbsrv.exe PDB
type server. With 12 parallel link jobs this races the type-server cap
(LNK1318 "LIMIT (12)") and exhausts the runner disk (LNK1180), recurringly
failing the Windows release CI. CI needs no debug info, so disable PDB
generation for the dev/test profiles in this job only.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: constrain unauthenticated get_public_resource to app_theme resources
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: remove get_public_resource regression test
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: bind /etc resolver files read-only in nsjail sandboxes
* docs(nsjail): explain why per-file /etc resolver binds are load-bearing
The explicit /etc/hosts, /etc/resolv.conf and /etc/hostname binds look
like removable duplication of the read-only /etc bind above them. They
are not: on Kubernetes those files are separate kubelet bind-mounts on
top of /etc and nsjail's read-only remount is non-recursive, so without
these shadow binds they stay writable and a job can persist cross-tenant
DNS poisoning for the pod lifetime. Comment guards against a future
"dedup cleanup" silently reintroducing the vulnerability.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(nsjail): shorten the load-bearing-bind comment to 3 lines
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix: sidebar menu rendering expanded-but-empty near 768px width
The desktop sidebar branch is gated by JS (`innerWidth < 768`), but its
width was set only via Tailwind `md:` classes (`@media (min-width:768px)`).
`window.innerWidth` rounds fractional viewport widths, so at e.g. 767.8px
JS rounds to 768 and renders the desktop sidebar, while the CSS media
query does not match and no width class applies — leaving the sidebar
shell expanded with no width/content. Drop the now-redundant `md:`
prefix so width tracks the JS branch decision.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: align content offset breakpoint with sidebar JS gate
The sidebar width now follows the JS innerWidth gate, but the main
content left-offset in AiChatLayout still used the `md:` CSS media
query, leaving the two breakpoints out of sync in the same sub-pixel
band. Pass an `isMobile` flag from the layout (mirroring the sidebar's
`innerWidth < 768` condition) and gate the content padding on it with
unprefixed classes so sidebar width and content offset always flip
together.
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: reset parent_hash in auto_parent when all versions at path are archived
* test: regression test for auto_parent with all versions archived
---------
Co-authored-by: windmill-internal-app[bot] <1429786+windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* fix: don't show ALLOW_PRIVATE_AI_BASE_URLS hint for malformed AI base URLs
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: impl std::error::Error for SsrfValidationError for anyhow callers
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix: atomic bundle cache writes to prevent parallel cold-load race
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: trust-but-replace in atomic_publish_dir to never trust a stale partial cache dir
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: simplify atomic_publish_dir and add content-addressed rename-failure fallback
Revert the destroy-then-recreate dir swap (introduced concurrent-publisher
edge cases: spurious Err under a real herd, EACCES masking a stale partial)
back to a single atomic rename. Add the content-addressed exists-fallback to
atomic_write_file_bytes/atomic_copy_file so the loser of a publish race (and
Windows, where rename cannot replace an open/existing destination) treats the
already-published identical cache as success instead of failing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
windmill-parser-ts-asset only recognized writeS3File/loadS3File when
the first arg was a bare 's3://...' string literal. The actual SDK
signature takes an S3Object ({ s3, storage? }) or 's3://bucket/key'
string, which every real script uses, so object-form writes/reads were
never detected as assets. Resolve the S3Object arg the same way the
runtime parseS3Object does, mapping { s3, storage } to s3://<storage>/
<key> and feeding it through parse_asset_syntax so the path matches the
// on s3:///… trigger form. Adds regression tests.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: aggregate wait time should target the true root job, not flow_innermost_root_job
* refactor: reuse get_root_job_id helper for wait-time aggregation
Instead of duplicating the root_job → flow_innermost_root_job →
parent_job fallback chain inline, call the existing get_root_job_id()
helper (the same one used by push_next_flow_job) and filter out the
self-id case so standalone scripts still skip aggregate insertion.
Behaviorally identical, single source of truth.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: windmill-internal-app[bot] <1429786+windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] fix(nativets): pass tracing-enabled OtelConfig to deno_telemetry::init
* chore: update ee-repo-ref to 19a76a09ffb43649ee19e62d07e8b8a42d78757b
This commit updates the EE repository reference after PR #573 was merged in windmill-ee-private.
Previous ee-repo-ref: daf7ce31f192d64ca6b5602af626ab6453d2aff4
New ee-repo-ref: 19a76a09ffb43649ee19e62d07e8b8a42d78757b
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <1429786+windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: include service accounts in instance settings users list
Service accounts (workspace-scoped, no password row) now appear in the
superadmin users list with a Bot icon, workspace badge, and a link to
manage them in the workspace settings. Role is locked to Operator.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update sqlx offline cache
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: use composite key for users each block
Service accounts can share emails across workspaces, so key by email + workspace_id to avoid Svelte each_key_duplicate.
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(python): preserve strings containing Infinity/NaN in result JSON
* test(python): add sanity checks for Infinity/NaN in results
* refactor(python): use string-aware regex callback for single-pass cleanup
* refactor(python): compact regex + handle backslash-escape parity
* perf(python): short-circuit cleanup when no Infinity/NaN/NUL in result
* feat: read-only flag on API tokens, orthogonal to scopes
Add a per-token `read_only` boolean set at creation time. When true, the
token can only call HTTP methods classified as Read (GET/HEAD/OPTIONS).
Mutating methods and job-run actions are rejected with 403, regardless of
which scopes are attached. Surfaced as a prominent toggle in the standard
token-creation flow and a discreet `2xs` toggle in MCP mode (where users
often want write access, so we don't bias them toward enabling it).
MCP enforcement: read-only tokens hide all script/flow/hub tools from
`list_tools` and only see endpoint tools whose method is GET, and the
runner rejects `call_tool` on anything mutating.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: review fixes for read-only token flag
- Exempt /api/mcp/* and /mcp/* paths from the read-only middleware check.
MCP transport runs over POST (streamable HTTP / SSE), so otherwise the
middleware would 403 every MCP request before the runner could enforce
read-only at the tool-call level.
- Tighten is_endpoint_read_only to GET only, matching the read_only_hint
that create_endpoint_annotations actually emits.
- Add unit test for check_read_only_for_route covering GET/HEAD/OPTIONS,
mutating methods, and run paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref to read-only-trigger-toggle
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): make read-only toggle discreet in both modes
Match the MCP-mode treatment in standard mode: text-tertiary, 2xs, shared
"Read-only" label. The tooltip switches per mode so the explanation still
fits the context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): gate read-only toggle behind Limit token permissions
The read-only toggle now only shows when the user has limited the token's
scopes (standard mode) or in MCP mode (which always picks an MCP scope).
Turning the limit off also resets read-only so it doesn't silently stick.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): hide incompatible MCP tools when read-only is on
When the read-only toggle is on in MCP mode:
- Endpoint badges and the custom-mode endpoint MultiSelect filter to GET.
- Already-selected non-GET endpoints are pruned from the scope.
- The scripts/flows preview is replaced with a note explaining they're
hidden (the runner already rejects script/flow runs for read-only).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): place read-only toggle at top of limited scope area
The previous gate required at least one scope to be picked before the
read-only toggle appeared, which made it look missing while the user was
still building their scope list. Move the toggle inside ScopesPicker:
- Standard mode: sits directly under the "Limit token permissions" toggle
whenever Limit is on, before the scope selector.
- MCP mode: sits at the top of the MCP scope block.
readOnly is now $bindable on ScopesPicker so CreateToken still owns the
value. The auto-reset on un-limit moves into ScopesPicker too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): nest read-only toggle inside the scope list card
Place the read-only toggle at the top of the scope list (between the
Selected Scopes summary and the bordered domain list) via a new optional
topSlot snippet on ScopeSelector. Keeps ScopeSelector decoupled from
read-only specifics; ScopesPicker fills the slot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 9bc8160be50b3e57a60daf4e1b71c389a6e02b8a
This commit updates the EE repository reference after PR #571 was merged in windmill-ee-private.
Previous ee-repo-ref: f53d26e6685dfd60bfa67686fbd7358169cfd130
New ee-repo-ref: 9bc8160be50b3e57a60daf4e1b71c389a6e02b8a
Automated by sync-ee-ref workflow.
* fix: address CI review for read-only token flag
- P1 (Codex): narrow the MCP middleware exemption from "any /api/mcp/*"
to just the streamable HTTP transport endpoints
(/api/mcp/gateway, /api/mcp/w/{ws}/{mcp,sse,list_tools}). Without this,
a read-only token could POST /api/mcp/gateway/oauth/server/approve and
mint a follow-on non-read-only MCP token via the OAuth code/token
exchange.
- P2 (Claude/cubic): fix test comment/assertion mismatch — the run-path
assertion now exercises GET (which is what the RUN_PATH_ACTIONS
elevation comment describes) in addition to POST. Add a regression
assertion for /api/mcp/gateway/oauth/server/approve.
- P2 (cubic): short-circuit script/flow/hub-script/resource fetches in
MCP list_tools when read_only is on — they would only be discarded
below, so skipping the DB and resource fan-out is pure win.
- P2 (cubic): when scopes are pre-supplied via the CreateToken prop, the
ScopesPicker isn't rendered, which previously hid the read-only
toggle entirely. Render it next to the pre-supplied scopes display.
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>
- exists_script_by_path now filters archived = false, matching the
conflict check in create_script_internal. Previously the frontend
blocked creating a new script at a path occupied only by archived
scripts, even though renaming to that same path was allowed.
- Hide the Delete entry in the script details "..." menu unless the
user is admin. The backend delete_script_by_hash already requires
admin, so non-admins would always see an error after clicking.
* perf(dynselect): only retrigger when helper-script args actually change
Parse the inline helper's signature with the existing WASM parser and
restrict the form-arg diff to keys the helper actually consumes. Typing
into unrelated fields no longer queues a dynselect job every second.
Falls back to the previous full-args comparison when the helper is
deployed or parsing fails.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(dynselect): avoid double helper-script fetch on mount
usePromise defaults to loadInit=true, so refresh() ran before the
JobLoader child was bound (firing a no-op pending promise) and the
$effect then fired a second refresh once the bind:this resolved.
Disable loadInit so the effect owns the single first call.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(dynselect): use parser directly instead of inferArgs
inferArgs mutates a Schema object we never use and goes through a
shared cache; when fed an empty schema for non-main entrypoints the
caller cannot reliably read back the resulting properties. Add
parseEntrypointArgs that just runs the parser and returns the
parameter name Set (or undefined when unknown / unsupported / has
rest args / function not found). DynamicInput uses that and keeps
the previous params in flight while the next parse is computing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(dynselect): support deployed helpers in smart retrigger
Add getHelperEntrypointArgs which dispatches on HelperScript.source:
inline parses immediately; deployed fetches the script (or the flow's
inline dyn-select code) once and caches per (workspace, kind, path,
entrypoint). Without this the /scripts/get/* run view fell back to
the full-args comparison and still retriggered on unrelated fields.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(dynselect): zero-arg helpers report empty deps, not unknown
Codex review flagged that a valid zero-parameter entrypoint was being
treated as "couldn't determine signature" and falling back to the
full-args comparison. Distinguish "function found with no params" from
"function not found" via the parser's auto_kind field — only the
latter sets it, so empty args + auto_kind=null means a real zero-arg
helper and we return an empty Set (no retrigger on unrelated fields).
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(bun): pass --preserve-symlinks on unbundled execution
Bun 1.2/1.3 moved its global package cache to a content-addressed
layout and the installer now creates a single directory symlink from
node_modules/<pkg> to the cache entry. Without --preserve-symlinks,
Bun resolves modules from each file's realpath, so any require/import
inside an installed package walks up from cache_nomount/bun/... and
never finds the sibling deps living under <job_dir>/node_modules/.
This manifested as e.g.
ENOENT while resolving package 'zod/v3' from
'/tmp/windmill/cache_nomount/bun/@langchain/core@1.1.44@@@1/dist/...'
on //nobundling scripts that pull @langchain/core, even though zod is
correctly installed alongside it in node_modules.
The bundled execution path already had --preserve-symlinks since #4132
(needed because we symlink the cached bundle file into the job dir).
The unbundled path didn't, because at the time Bun installed via per-
file hardlinks and the realpath of node_modules entries was the job
dir itself. The Bun installer's layout change made the flag necessary
on the unbundled path as well.
Add the flag to all three unbundled `bun run` invocations:
- nsjail unbundled path
- non-nsjail unbundled path
- dedicated worker (always unbundled)
This also fixes a latent bug on the first run of any bun script that
imports a package whose internals reference siblings (the build_cache
path runs unbundled this round while it builds the bundle for next
time).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(bun): regression test for nobundling + transitive require resolution
Adds an integration test that mirrors the original failure: a //nobundling
script importing @langchain/core, which (in its CJS internals) does
require('zod/v3'). Before --preserve-symlinks was added to the unbundled
bun run invocations, this failed with:
ENOENT while resolving package 'zod/v3' from
'.../cache_nomount/bun/@langchain/core@<ver>@@@1/dist/runnables/base.js'
The test covers the non-nsjail unbundled path. Reproducibility of the
pre-fix failure depends on Bun's installer choosing the directory-symlink
layout for the node_modules entry (the default on Bun 1.2/1.3+ with the
new content-addressed global cache that produced the user's error).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(git-sync): regression tests for secondary promotion repos
Adds two integration tests that reproduce the bug where a second
promotion-mode repo's deployment callback was silently dropped via
debounce-key collision, plus the EE ref bump that includes the fix.
Updates the two existing promotion-mode debounce-key tests to expect
the new repo-namespaced key shape.
* test(git-sync): drop redundant distinct-debounce-keys test
The behavior test (`test_two_promotion_repos_both_enqueue_callback`)
already covers the same regression one layer up: if the debounce keys
collide, one callback gets marked skipped, which the behavior test
catches.
* chore: update ee-repo-ref to 7a32388adaa37eb1dd1820b40e140ff1877110f2
This commit updates the EE repository reference after PR #572 was merged in windmill-ee-private.
Previous ee-repo-ref: dbf26f5e4c01c0de536f606679be46eb316aaf31
New ee-repo-ref: 7a32388adaa37eb1dd1820b40e140ff1877110f2
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: publish CLI skills + AGENTS.md to windmill-cli-docs for context7
Auto-generates a public docs snapshot (AGENTS.md, full CLI reference,
all rendered skills) and pushes it to windmill-labs/windmill-cli-docs on
every release tag, so context7 can index Windmill CLI docs.
- generate.py: new --context7-dir flag rendering fully-resolved skills
+ AGENTS.md (extracted from cli/src/guidance/core.ts to avoid drift)
+ cli-commands.md + README.md + manifest.json into a docs-repo checkout.
Preserves .git, .github, LICENSE, context7.json across regenerations.
- publish-cli-docs.yml: GitHub Action on v* tag and workflow_dispatch
that regenerates the docs repo and pushes via the CLI_DOCS_DEPLOY_KEY
SSH deploy key.
* fix: skip tag mirror on workflow_dispatch from non-tag ref
* docs: turn windmill-cli-docs README into a CLI quickstart
* fix: address PR review (target safety, regex anchor, concurrency, tag mirror)
- Refuse to wipe --context7-dir unless empty, has a context7 marker, or
points at the windmill-cli-docs remote (P1, prevents typo blast).
- Anchor AGENTS.md template regex on `generateAgentsMdContent` so adding
other template-returning functions to core.ts can't silently retarget it.
- Decode TS escapes in one pass to avoid order-sensitive mangling.
- Include Windmill version (from version.txt) in manifest.json so each
snapshot is self-describing.
- Add concurrency group on the publish workflow.
- Always mirror version tag on tag pushes, even when content is unchanged,
so the docs repo has a tag for every Windmill release.
- Expand preserve list with .gitignore, .gitattributes, CODEOWNERS.
* fix: validate manifest.json content, not just presence, before wipe
* feat: add EditableInput component
* feat: add EditorHeader for flow editor with file picker entry point
* feat: WorkspaceItemPicker for editor header navigation
* feat: clickable breadcrumb in EditorHeader, scoped picker keyboard nav
* fix: reload flow on URL change and reset highlight in search mode
* feat: editor header layout polish and trigger removal
* feat: apply EditorHeader to script, app, and raw app editors
* fix: show generated initial path for new apps in EditorHeader
* fix: align EditorHeader new-app path with draft drawer's Path component
* fix: read page.params.path in loadApp to reload on URL change
* fix: remount AppEditor when navigating between apps
* fix: clear app/files on URL change so editor remounts with fresh data
* fix: route picker selections to /apps/edit or /apps_raw/edit based on raw_app
* fix: drop Save button from path popover; defer rename to deploy
* refactor: drop pathPopoverContent snippet, bind newEditedPath directly
* refactor: dedupe editor header plumbing (editPathFor, userPathPrefix, breadcrumb snippet)
* fix: freeze breadcrumb during path edit so popover doesn't drift
* fix: drop spinner from path dependency-check; render nothing when no usages
* fix: swallow 404 in checkFlowOnBehalfOf so renaming a flow doesn't toast
* Revert "fix: swallow 404 in checkFlowOnBehalfOf so renaming a flow doesn't toast"
This reverts commit 82dec462ae.
* refactor: drop moveRenameManager dep from EditorHeader; pass onBehalfOfEmail as prop
* refactor: replace breadcrumb-snapshot effect with open/close setter
* refactor: drop unused dirtyPath state from EditorHeader
* fix: surface Path validation error in pen popover
* fix: decouple Path validation error from hideFullPath toggle
* refactor: use InputError for path validation message (slide transition)
* fix: re-derive Path meta from external path changes (sibling sync)
* docs: note Path's meta could be replaced with function-form bindings
* fix: 'Exit & see details' uses deployed path, not live store
* fix: undo/redo shortcut uses shiftKey instead of fragile case-match
* fix: type errors and keep edit pen visible while popover is open
* chore: remove unused meltComponents/Accordion wrapper
* fix: focus search input on picker open via popover openFocus selector
* fix: refocus picker search input on every popover open
* fix: pre-seed picker loaded state from cache so accordion opens at the right place
* fix: breadcrumb and picker track savedPath, not draft-renamed live path
* fix: inject current draft item into picker so breadcrumb scope isn't empty
* docs: add component-level and prop-level docs to EditableInput
* feat: warn that a deploy is needed when path is edited on a saved item
* feat: show same path-change-needs-deploy message in flow/script settings
* fix(flows): persist draft-renamed path through reload and dirty check
* fix: include path in unsaved-changes diff so renames trigger the modal
* feat: nested folders in picker tree and breadcrumb
* fix: per-segment popover state so switching breadcrumbs closes the previous one
* refactor: replace accordion picker with drill-through picker
* refactor(picker): review fixes, drill polish, and breadcrumb collapse
* fix(picker): review fixes — banned bindable, drop sibling-sync, load races, mouse highlight
* fix(picker): smooth-scroll highlighted row into view on open
* fix(picker): second-pass review fixes — load races, cache invalidation, breadcrumb/picker a11y, raw_app routing
* fix(RowIcon): apply size prop to resource_type and fallback divs
* feat(picker): add cross-kind 'All' root; deeper breadcrumb segments open there
* fix(picker,editor): third-pass review — search loading state, pen autofocus, allow empty summary, a11y
* fix(picker,editor): fourth-pass review — own check, flowbuilder arrows, editableinput double-save, customui.path gate, invalidate races
* fix(editor,picker): codex review — fresh URL state per load, granular whitelabel topBar gates
* fix(flows): clear localStorage in auto-reload to break URL-state loop
Setting a typed instance setting (e.g. `object_store_cache_config`) to
`null` via `PUT /api/settings/instance_config` was silently ignored:
`Json<InstanceConfig>` deserialized null to `None`, and `to_settings_map`
then dropped it via `skip_serializing_if = "Option::is_none"`, so
`diff_global_settings` (Merge mode) never saw the deletion.
Add `#[serde(remote = "Self")]` to `GlobalSettings` plus a manual
`Deserialize` impl that captures top-level explicit nulls into `extra`,
which already round-trips correctly via flatten. The handler stays
fully typed.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): branch download UI on shouldDownloadViaClient instead of intercepting in onclick
When OpenAPI.TOKEN is set, several download links rendered an `<a href>`
to the API and relied on an `onclick` handler to call `e.preventDefault()`
and route the request through `downloadViaClient`. This is fragile in
embedded contexts (e.g. the whitelabel React SDK) where Svelte's hydrated
event listener may not intercept the click in time, so the browser
follows the unauthenticated `href` straight to the API.
Mirror the drawer pattern already used in `LogViewer` and
`FlowStatusViewerInner`: render a `<button>` calling `downloadViaClient`
when `shouldDownloadViaClient()` is true, and fall back to the plain
`<a href download>` otherwise. Affects the LogViewer top bar, the large-
result download in DisplayResult, the inline S3 link in ObjectViewer,
the CSV link in ParqetCsvTableRenderer, and FileDownload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): treat custom HEADERS / basic auth as token equivalents in shouldDownloadViaClient
Customers wiring the SDK with cookie-bypass auth via custom request
headers (e.g. `OpenAPI.HEADERS = getAuthHeaders()` returning a Bearer
header) had `shouldDownloadViaClient()` return false because it only
checked `OpenAPI.TOKEN`. The plain `<a href download>` branch then
followed the link without those headers, so authenticated downloads
silently degraded to the cookie path (or failed when there is no cookie).
Widen the check to any non-cookie auth: TOKEN, HEADERS, or USERNAME
(basic auth). Route `downloadViaClient` through the generated client's
`getHeaders` so all configured auth schemes are applied consistently
instead of hand-building an Authorization header for TOKEN only.
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>
json_to_document cast any i64 to u64 via `as` and wrapped in PosInt.
Negative integers in a tool's JSON schema (e.g. "default": -1,
"minimum": -1) became huge unsigned values like 18446744073709551615,
which Bedrock rejected with `tools.N.custom.input_schema: int too big
to convert`.
Try as_u64 first to preserve full u64 range, fall back to as_i64 for
negatives via NegInt, then as_f64.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] fix(license): bump ee-repo-ref to fix CE build
#9089 introduced a call from `windmill-api-settings/src/ee.rs::validate_license_key`
(license-gated) to `windmill_common::ee::compute_instance_hash` (enterprise-gated).
CE builds compile the caller (private + license) but not the callee, breaking the
docker image build on main with E0425 since #9089 landed.
Companion EE PR drops the enterprise gate from `compute_instance_hash`. The
function only depends on common state (BASE_URL, get_instance_uid,
canonical_base_url) and the parent `ee` module is already gated by `private`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to f9494c6320bb5fd07c1e9e09734b7fd5fbe7aa38
This commit updates the EE repository reference after PR #569 was merged in windmill-ee-private.
Previous ee-repo-ref: 0f495da1ef984cad22342c90691c0dc8b0a20faa
New ee-repo-ref: f9494c6320bb5fd07c1e9e09734b7fd5fbe7aa38
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>
* [ee] feat(license): offline (URL-bound) license keys
Offline keys are a 4-segment variant for air-gapped customers — no
phone-home, embedded seat/CU caps, locked to the instance's base_url.
Existing 3-segment online keys are unchanged.
Companion PRs:
- windmill-labs/windmill-ee-private (full design + EE impl)
- windmill-labs/windmill-customer-service (issuance + portal)
- windmill-labs/windmill-cf-worker-keygen (signing)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] refactor(license): bind offline keys via instance hash; simpler CU enforcement
- /settings/license_status now surfaces an `instance_hash` superadmins share
with support when requesting an offline key
- OfflineMetadata: `hash` replaces `base_url`; OfflineCapStatus reports
`current_cu` (last 2min) and drops the grace-period fields
- verify_license_key now takes a db so EE can recheck the hash
- InstanceSetting.svelte: hash copy-block + simpler status panel
- Bump ee-repo-ref
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] chore(license): bump ee-repo-ref
Pulls in the current_cu clamp + prod public key restoration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] refactor(license): split instance_hash endpoint; minimal cap UI; restore workers expiry toast
- `instance_hash` is no longer part of /settings/license_status responses; it
lives at GET /settings/instance_hash (super-admin only) so it isn't re-emitted
on every status poll. The UI doesn't show it — admins fetch it explicitly when
requesting a key from support.
- InstanceSetting offline cap UI is now two compact green/red status lines
(Seats X.X/Y and CUs X.X/Y) placed above the action buttons, matching the
existing "Latest key renewal" badge style. The block-panel is gone.
- "Latest key renewal" line and the "Renew key" button are now hidden when an
offline key is loaded (renewal is server-disabled for offline keys).
- Restore parseLicenseKey + checkLicenseExpiration toast on /workers
(works for both 3- and 4-segment keys).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] chore(license): bump ee-repo-ref
Pulls in the plain-SHA256 instance hash + stats_ee revert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] chore(license): bump ee-repo-ref
Picks up the alert wording change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] chore(license): bump ee-repo-ref
Picks up the instance_uid cache so the periodic verify_license_key cycle
no longer hits global_settings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] refactor(license): rename /settings/license_status → /offline_license_status
The endpoint was only used by the offline-license UI; the other fields it
returned (license_key_id, license_key_valid, kind, offline metadata) were
unused. Rename to clarify scope and flatten the response — it now returns
just the OfflineCapStatus (or null when no offline license is loaded).
Frontend uses `offlineCapStatus != null` as the "is offline" check.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] fix(ci): regenerate sqlx cache for the inline worker_ping query
After reverting unused stats_ee helpers (fetch_worker_pings*), the
inline `sqlx::query_as!(WorkerPingRecord, ...)` in get_stats_payload
lost its cache entry — CI's check_ee_full + cargo_test were failing
under SQLX_OFFLINE=true with E0282 type-inference errors.
Re-running update_sqlx.sh regenerates the cache file under its
current hash and prunes a couple of stale entries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] fix(license): address cubic-bot review
- get_offline_license_status: propagate enforce_offline_caps errors as 500
instead of swallowing into a "no offline license" (Option::None) response
- canonical_base_url: rewrite the doc to match the actual fallback behavior
(lowercase + trailing-slash strip on URL parse failure); the original
cross-service contract is gone since the customer-service no longer
canonicalizes (treats the instance hash as opaque)
- check_seat_cap_for_new_user: take an email and short-circuit when the
email is already in `usr ∪ workspace_invite` so net-zero invite upserts
and invite→user transitions aren't spuriously blocked at cap. Mirrors
the dedup rule the count itself uses.
- Bump ee-repo-ref to pull in the EE-side change
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] chore(license): bump ee-repo-ref
Picks up the exact-delta seat-cap check (replaces the simple existence
short-circuit). Regenerates the new sqlx cache for the bool_and query.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [ee] fix(license): propagate get_instance_hash errors; bump ee-repo-ref
- get_instance_hash: replace `.ok().flatten()` with map_err+? so DB errors
during instance_uid lookup surface as 500 instead of silently returning
`{"instance_hash": null}` (same pattern get_offline_license_status already uses)
- Bump ee-repo-ref to pull in the enforce_offline_caps cached-state preservation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to c6cd1afe2d9e04809b30751cd1687b28a65e62b1
This commit updates the EE repository reference after PR #566 was merged in windmill-ee-private.
Previous ee-repo-ref: a6d91016ae0d43c46604313aecae3aa9c778c8e0
New ee-repo-ref: c6cd1afe2d9e04809b30751cd1687b28a65e62b1
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* chore: narrow secret-file Read deny rule to dotfiles/extensions
* feat(vault): configurable JWT auth mount path and fix setup docs
* chore: bump ee-repo-ref for vault jwt mount path
* chore: bump ee-repo-ref after rebase onto EE main
* chore: update ee-repo-ref to a1cd60b54e8595b4e5ce6b654e675e4bbe2253b2
This commit updates the EE repository reference after PR #567 was merged in windmill-ee-private.
Previous ee-repo-ref: c274f233a0ebb54afa296c3db15ff330e1baebcf
New ee-repo-ref: a1cd60b54e8595b4e5ce6b654e675e4bbe2253b2
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* deps: pin tokio-postgres to MaterializeInc fork to fix query_typed_raw deadlock
`pg_executor`'s `Client::query_typed_raw` (and `Client::prepare` on the
streaming path) deadlock when the result schema contains a column whose
Oid the client doesn't know yet — citext, custom enums, custom domains,
postgis types. Easy to reproduce against any partitioned table with a
citext column: ~100+ rows is enough on localhost, less on slower links.
`psql` works fine for the same query because the simple-query protocol
doesn't trigger the typeinfo lookup path.
## Root cause (unchanged tokio-postgres bug for years)
`query::query_typed` calls `get_type(client, oid).await` synchronously
while still holding the original query's `Responses` stream. The
original query's `DataRow`s back up in the per-request
`mpsc::channel(1)`, `Connection::poll_read` stops draining the wire,
and the typeinfo sub-query response (queued on the same socket behind
those DataRows) never arrives. Classic head-of-line blocking.
## Fix
Pin `tokio-postgres` / `postgres-types` / `postgres-protocol` (via
[patch.crates-io]) and the workspace `rust-postgres` /
`rust-postgres-native-tls` aliases to the
[MaterializeInc rust-postgres fork at `78c1222577`](https://github.com/MaterializeInc/rust-postgres/tree/master).
MI's [PR #33 "bigger-channels"](https://github.com/MaterializeInc/rust-postgres/pull/33)
(merged 2025-12-11) resized the per-request response channel from
`mpsc::channel(1)` → `mpsc::channel(1024)`. That gives the connection
task 1024 batches of headroom while a streaming consumer is paused
mid-stream — orders of magnitude more than realistic typeinfo
deferral needs (≈3 batches).
## Why MaterializeInc and not a windmill-labs fork
`windmill-trigger-postgres` already depended on the imor fork for the
`postgres-replication` crate (logical replication: `CopyBothDuplex`,
`LogicalReplicationStream`, `TupleData` decoding including binary
tuples). That crate has never been on upstream rust-postgres — petrosagg's
[PR #752](https://github.com/rust-postgres/rust-postgres/pull/752) was
closed in 2021 in favour of a smaller split,
[PR #778](https://github.com/rust-postgres/rust-postgres/pull/778) is
still open today after five years. petrosagg keeps the replication work
alive on the MaterializeInc fork.
MaterializeInc is a strict superset of what we previously got from imor:
- imor's binary-tuple commit (sha `20265ef38e`) was merged into MI master.
- petrosagg has added perf + correctness fixes on top (allocation reuse,
proper decoding fixes).
- The deadlock mitigation (`channel(1024)`) was added three weeks before
this issue surfaced.
MI tracks upstream rust-postgres with a periodic catch-up merge (12-18 mo
cadence; last on 2025-12-03, ~100 commits picked up). Not an abandoned
fork.
## Why this works now (didn't on earlier attempt)
A previous attempt at this PR (`248ccb5a97`) hit CI failure because the
MI fork's `postgres-types 0.2.11` requires `serde_core ^1.0.221`, but
Windmill's workspace pinned `serde = "=1.0.220"` for swc_common 0.37.5's
`pub use serde::__private as serde;` hack. Bumping serde above 1.0.220
broke the swc_ecma_ast `Deserialize` derive under the
`enterprise,deno_core,…` feature set.
The earlier blocker is now resolved by #9111 which bumped the deno +
swc pin set to a "goldilocks" combination where `swc_common 14.0.4`
drops the `__private` hack, freeing the workspace serde pin to `^1`.
serde now resolves to 1.0.228, which satisfies MI's `serde_core ^1.0.221`
requirement transitively — no extra workspace pin needed.
## Diff shape
Two files only:
- `backend/Cargo.toml` (+34/-1): three new `[patch.crates-io]` entries
(`tokio-postgres`, `postgres-types`, `postgres-protocol` → MI fork)
plus comment block, plus the two workspace deps (`rust-postgres` /
`rust-postgres-native-tls`) repointed from imor's fork to MI's.
- `backend/Cargo.lock` — auto-regenerated. Replaces all `imor/rust-postgres`
references with `MaterializeInc/rust-postgres`, bumps the affected
crate versions to MI's set (tokio-postgres 0.7.11 → 0.7.15,
postgres-types 0.2.7 → 0.2.11, postgres-protocol 0.6.7 → 0.6.9,
postgres-native-tls 0.5.0 → 0.5.2). No source code changes.
## Verification
- `cargo check --features quickjs` → clean.
- `cargo check -p windmill-worker --features quickjs` → clean (pg_executor
builds).
- Repro tested earlier in the thread that produced this PR: the
partitioned-citext-table query on Neon goes from "hangs indefinitely"
(server idle on `wait_event=ClientRead` while client awaits typeinfo
behind undrained DataRows) to "completes in ~1.0s, 100 rows" with the
MI fork's `bounded(1024)` response channel.
## Caveats
- **`bounded(1024)` is a mitigation, not a closure.** Theoretical failure
mode remains at >~64 MB single-query results with a custom-Oid column
(typeinfo defers for >1024 batches of ~64 KB each). The strict-correct
fix is `mpsc::unbounded()` — proposed as a follow-up PR to MI. For
realistic Windmill workloads, 1024 batches of headroom is well past
the ~3-batch typeinfo deferral that's actually needed.
- **`postgres-replication` is now upstream-of-fork's only home.** No
realistic path to upstream rust-postgres merging it. The MI pin is
intended to stay in place until either upstream changes course
(unlikely) or MI publishes to crates.io (also unlikely — they don't
publish releases of the fork).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
* chore: update ee-repo-ref to 8fe0d290fb0b71c24184eb5ad99bbdc7c813697c
This commit updates the EE repository reference after PR #568 was merged in windmill-ee-private.
Previous ee-repo-ref: 4d01d171228196f28ddabc1150242bfab623d5cf
New ee-repo-ref: 8fe0d290fb0b71c24184eb5ad99bbdc7c813697c
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* deps: bump deno_core / deno_ast / swc to the goldilocks pin set; drop serde ceiling
Bumps every deno_* and swc_* workspace dep to a hand-picked "goldilocks"
combination that drops the serde =1.0.220 ceiling without crashing into
the rustls / aws-sdk resolver wall that the obvious deno v2.6.0 target
hits.
## What's the goldilocks set
| crate | old | new | source |
|------------------|----------|-----------|---------------------------------------|
| deno_core | 0.336.0 | 0.352.0 | deno v2.4.0 |
| deno_fetch | 0.214.0 | 0.233.0 | deno v2.4.0 |
| deno_tls | 0.177.0 | 0.196.0 | deno v2.4.0 (last permissive-rustls) |
| deno_console | 0.190.0 | 0.209.0 | deno v2.4.0 |
| deno_url | 0.190.0 | 0.209.0 | deno v2.4.0 |
| deno_webidl | 0.190.0 | 0.209.0 | deno v2.4.0 |
| deno_web | 0.221.0 | 0.240.0 | deno v2.4.0 |
| deno_io | 0.100.0 | 0.119.0 | deno v2.4.0 |
| deno_net | 0.182.0 | 0.201.0 | deno v2.4.0 |
| deno_permissions | 0.49.0 | 0.68.0 | deno v2.4.0 |
| deno_telemetry | 0.12.0 | 0.31.0 | deno v2.4.0 |
| deno_error | =0.5.5 | =0.6.1 | deno v2.4.0 |
| deno_ast | =0.44.0 | =0.51.0 | **override** — see "load-bearing" below |
| deno_fs | (new) | 0.119.0 | new workspace dep — FetchPermissions exposes deno_fs::CheckedPath / GetPath as public API |
| v8 | =130.0.7 | =137.1.0 | deno_core 0.352 transitive |
| swc_common | =0.37.5 | =14.0.4 | **the load-bearing pin** |
| swc_ecma_ast | =0.118.2 | =15.0.0 | matched set with swc_common 14.0.4 |
| swc_ecma_parser | =0.149.1 | =24.0.3 | matched set |
| swc_ecma_visit | =0.104.8 | =15.0.0 | matched set |
| serde | =1.0.220 | ^1 | **freed** (resolves to 1.0.228+) |
## Why this combination and not v2.6.0
The obvious target was deno v2.6.0 (with deno_ast 0.52 → swc_common 17,
well past the `__private` ceiling). That hits three resolver collisions:
1. libsqlite3-sys: deno_cache → rusqlite 0.37 → libsqlite3-sys 0.35
vs sqlx → libsqlite3-sys 0.30. **Already killed by PR #9110** —
we dropped deno_runtime, which is what pulled in deno_cache.
2. fqdn 0.4.6/0.4.7 yanked, required by deno_permissions 0.81.0. Solvable
by injecting the yanked entry into Cargo.lock manually but ugly.
3. rustls: deno_tls 0.198+ hard-pins `=0.23.28`, but aws-sdk-bedrockruntime
1.122.0 → aws-smithy-http-client 1.1.5 wants `^0.23.31`. Within-major
conflict, no resolver path. The unbeatable wall.
Goldilocks-set choice sidesteps (2) and (3) entirely:
- `deno_tls 0.196.0` was the last version before deno tightened
`rustls ^0.23.11` (range, accepts 0.23.31) to exact `=0.23.28`. With
^0.23.11, the resolver picks rustls 0.23.35 (latest 0.23 patch) which
satisfies both deno_tls's `>=0.23.11` and aws-sdk's `>=0.23.31`. Verified
empirically: lockfile has rustls 0.23.35 after this bump.
- `deno_permissions 0.68.0` (v2.4.0's pin) doesn't depend on fqdn at all.
The fqdn dep was added in a later deno_permissions release.
## Why deno_ast =0.51.0 specifically (not 0.48.0 from v2.4.0)
`swc_common 14.0.4` is the first patch that **drops the
`pub use serde::__private as serde;` line** in `src/private/mod.rs`. Older
14.0.x and all 0.37.5–13.x revisions still have it, and that line is
what was capping `serde = "=1.0.220"` (the workspace pin's "stuck because
of swc" comment). Empirically verified by inspecting the tarballs of
14.0.0 / 14.0.1 / 14.0.2 / 14.0.3 / 14.0.4:
14.0.0: has hack
14.0.1: has hack
14.0.2: has hack
14.0.3: has hack
14.0.4: NO HACK ← inflection point
`deno_ast 0.51.0` pins `swc_common =14.0.4` exactly — older deno_ast
versions pin earlier swc_common patches that still have the hack.
Notably, deno v2.4.0 itself pins `deno_ast =0.48.0` (swc_common 9.2.0,
still has hack) — we deliberately deviate from v2.4.0's deno_ast pin
to escape the swc serde wall, while keeping the rest of v2.4.0's pin
set for resolver compatibility with aws-sdk. deno_ast 0.51 was never
shipped in any deno release (v2.4.5 used 0.49, v2.5.0 jumped to 0.50,
v2.6.0 to 0.52), but it's published on crates.io and compatible with
v2.4.0's deno_core 0.352.
## What this unblocks
- PR #9106's `serde = "=1.0.224"` bump variant can rebase onto this
and resolve cleanly (MaterializeInc/rust-postgres' `postgres-types`
needs `serde_core ^1.0.221`, which is satisfied now that we're on
serde 1.0.228).
- Future deno_* / swc_* bumps no longer need to argue about the serde
ceiling — it's gone.
## What changes in source code
This commit is Cargo.toml + Cargo.lock only. Source changes that the
new deno_core / deno_fetch API requires live in the follow-up commits:
- `parsers/windmill-parser-{ts,ts-asset,wac}`: swc 0.37 → 14
(`code.into()` ambiguity fix at 5 sites)
- `windmill-runtime-nativets/build.rs` + `src/lib.rs`: deno_core 0.336
→ 0.352 API moves (`init_ops_and_esm()` → `init()`,
`FetchPermissions` / `NetPermissions` trait signature updates,
`deno_tls::Proxy` enum shape change)
A companion change in windmill-ee-private adjusts
`otel_tracing_proxy_ee.rs:521` for `deno_telemetry::init`'s second arg
becoming by-value (was `&OtelConfig`).
* fix(parsers): adapt to swc_common 14 BytesStr ambiguity
swc 0.37.5 → 14.0.4 changed `SourceMap::new_source_file`'s `src` argument
from `String` to `impl Into<BytesStr>`. With `BytesStr` available, the
existing call sites' `code.into()` on a `&str` becomes ambiguous between
`Into<Bytes>` (from the bytes crate) and `Into<BytesStr>` (from
bytes_str). Switch to `code.to_string()` to produce an owned `String`
that satisfies `From<String> for BytesStr` unambiguously.
Five call sites across three crates:
- windmill-parser-ts/src/lib.rs (3 sites)
- windmill-parser-ts-asset/src/lib.rs (1 site)
- windmill-parser-wac/src/typescript.rs (1 site)
* fix(nativets): adapt to deno_core 0.352 / deno_fetch 0.233 API changes
The goldilocks deno bump (deno_core 0.336 → 0.352, deno_fetch 0.214 →
0.233, etc.) ripples through nativets' build.rs and src/lib.rs.
Source-level changes required:
## 1. `extension!` macro: `init_ops_and_esm()` and `init_ops()` removed
deno_core 0.352's `extension!` macro now generates a single `init()`
function on the extension struct (full: ops + esm), plus `lazy_init()`
(ops only, with `needs_lazy_init = true` and a contract that the
caller invokes `JsRuntime::lazy_init_extensions` after construction).
- `build.rs` (snapshot creation, wants both ops and esm baked in):
`X::init_ops_and_esm(...)` → `X::init(...)`.
- `src/lib.rs:create_nativets_runtime` (runtime, was using `init_ops()`
because the snapshot already provides esm): also → `X::init(...)`.
deno_core's snapshot path skips esm re-execution when the snapshot
provides them, so the esm registration is a no-op at runtime. This
is how deno's own v2.4.0 runtime works.
Avoided `lazy_init` because it requires plumbing
`JsRuntime::lazy_init_extensions(ext_args_vec)` correctly across the
codebase, which is invasive for no behavioural benefit.
## 2. Local `fetch` extension now declared in both build.rs and lib.rs
deno_core 0.352 validates extension order between snapshot and runtime.
Our snapshot's last extension is the local `fetch` ext (which provides
ext:fetch/src/runtime.js). To avoid a runtime panic:
"Extensions from snapshot loaded in wrong order: expected fetch but got windmill"
…the runtime extension list now ends with `fetch::init()` matching the
snapshot order. The macro requires the same `esm` argument to type-check,
even though the ESM is not re-executed at runtime (it's in the snapshot).
## 3. `FetchPermissions` and `NetPermissions` trait shape
`deno_fetch::FetchPermissions` (deno_fetch 0.233.0) added new methods
and changed signatures:
- `check_read` / `check_write`: now take `path: Cow<'a, Path>` plus
a new `get_path: &'a dyn deno_fs::GetPath` parameter, and return
`Result<deno_fs::CheckedPath<'a>, FsError>` instead of `Result<Cow<Path>, FsError>`.
- New `check_write` (didn't exist) and `check_net_vsock` methods.
`deno_net::NetPermissions` (deno_net 0.201.0) gained `check_vsock`
and `check_write_path` now takes `Cow<'_, Path>`.
For `build.rs`'s `PermissionsContainer` (used only during snapshot
creation, where permissions are never actually checked): all methods
`unreachable!("snapshotting")`.
For `src/lib.rs`'s `PermissionsContainer` (used at runtime — the
nativets policy is "allow everything"): `check_read` / `check_write`
return `Ok(CheckedPath::Unresolved(path))`, `check_*_vsock` return
`Ok(())`. Smoke tests confirm fetch/net/url/web/blob/timers/structuredClone
behaviour is intact end-to-end.
## 4. `deno_tls::Proxy` is now an enum
`deno_tls::Proxy` was a struct, is now an enum with `Http`, `Https`,
`Socks5` variants. Our call site uses HTTP proxies — switched the
struct literal `deno_tls::Proxy { url, basic_auth }` to
`deno_tls::Proxy::Http { url, basic_auth }`.
## 5. New `deno_fs` direct workspace dep
`FetchPermissions` exposes `deno_fs::CheckedPath` and `deno_fs::GetPath`
as part of its public API. We can't avoid naming `deno_fs` directly any
more. Pinned to 0.119.0 (v2.4.0's matched version, transitively present
already through deno_fetch). Added to workspace `[dependencies]` plus
nativets's `[dependencies]` and `[build-dependencies]`.
## Validation
`cargo check --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private,private_registry_test,csharp,php,ruby,mysql,quickjs,mcp,run_inline`
→ clean.
`cargo test -p windmill-runtime-nativets smoke -- --ignored --skip smoke_net_`
→ 8 passed; 0 failed (the full local smoke suite covering fetch,
setTimeout/Promise.all, URL/SearchParams, Blob/btoa/atob, large payload
roundtrip, error propagation, concurrent isolates, TS enum/union
transpile).
Network smoke tests (`smoke_net_fetch_example_com`,
`smoke_net_fetch_json_and_headers`) not run as part of the validation
gate but expected to pass — the change preserves deno_fetch behaviour
through the trait surface.
* chore: update ee-repo-ref to pick up deno_telemetry::init by-value fix
Points at windmill-ee-private branch deps/bump-deno-and-swc-goldilocks
which contains the companion otel_tracing_proxy_ee.rs adjustment for
deno_telemetry 0.12 → 0.31 (second arg of `init` is now by-value).
EE-only file, doesn't affect OSS build.
* chore(nix): bump rusty_v8 in flake.nix to 137.1.0 to match Cargo.toml
Cargo.toml's v8 pin moved from =130.0.7 to =137.1.0 as part of the
deno_core 0.336 → 0.352 bump, but I missed the comment directly above
the version pin:
# Exact version NOTE: Do not forget to update version and hash in flake.nix
flake.nix provides the prebuilt librusty_v8 binary that the v8 crate
links against. A version mismatch would either fail to fetch (if the
137.1.0 release didn't exist) or cause link-time symbol mismatches.
Nix is used by rust-client-check.yml and rust_on_release.yml in CI,
plus the dev shell — stale flake pin breaks all of those.
Updates x86_64-linux's sha256 to match the actual hash of
librusty_v8_release_x86_64-unknown-linux-gnu.a.gz at the 137.1.0 tag.
Other targets (aarch64-linux, x86_64-darwin, aarch64-darwin) remain
as lib.fakeHash — they were already placeholders in the previous
pin, so we don't regress on them.
Caught by both cubic and Pi reviewers on PR #9111.
* docs(nativets): clarify snapshot-prefix rule in extension-order comment
Claude reviewer caught that the doc comment claimed the runtime
extension list matches the snapshot's order — implying an exact match.
The truth is more permissive: deno_core 0.352 requires the snapshot's
extension list to be a *prefix* of the runtime's, not an exact match.
Runtime is allowed to append extra extensions (which we do — the
windmill `ext` carrying our ops is the last entry at runtime but absent
from the snapshot).
The code is correct as-is; only the comment wording was misleading.
Also fixes the same wording in PR description.
windmill-runtime-nativets was the workspace's only consumer of the
deno_runtime crate, and its only use of it was one call site in
build.rs:
deno_runtime::transpile::maybe_transpile_source(specifier, source)
That function (`deno_runtime-0.198.0/transpile.rs`, ~80 lines) is a pure
deno_ast + deno_core + deno_error wrapper — it doesn't touch any
deno_runtime state. Inline it verbatim into our build.rs and drop the
entire deno_runtime dep.
Why this matters now: deno_runtime transitively pulls in deno_cache →
rusqlite → libsqlite3-sys. From deno_cache 0.128.0 (Feb-Mar 2025)
onwards, rusqlite was bumped to ^0.34, which means libsqlite3-sys ^0.35.
sqlx 0.8 transitively requires libsqlite3-sys ^0.30. Cargo's `links =
"sqlite3"` rule allows only one libsqlite3-sys in a build graph, so the
two crates collide on any deno release ≥ v2.5. Inlining the transpile
helper sidesteps the collision entirely — sqlx-sqlite stays the sole
libsqlite3-sys consumer at 0.30.1.
All other appearances of "deno_runtime" in the source tree are for a
Windmill-internal function named `setup_deno_runtime`, not the crate.
Build artifacts validated:
- `cargo check --features quickjs` → green.
- `cargo test -p windmill-runtime-nativets smoke -- --ignored --skip smoke_net_`
→ 8 passed (the in-process V8 runtime + deno_fetch + deno_web + swc
transpilation surface still works end-to-end through the inlined
function).
- `cargo tree --invert deno_cache` → "did not match any packages"
(gone from the graph).
- Single `libsqlite3-sys` entry in Cargo.lock at 0.30.1 (sqlx's).
Renaming a flow step to 'Input', 'Result', or 'Trigger' would silently
corrupt the UI — the flow editor panel would switch to rendering the
special Input/Result/Trigger node instead of the step's config panel,
making the step inaccessible without editing YAML directly.
These virtual node IDs were already handled as reserved by
multiSelectUtils.ts but were missing from the forbiddenIds list that
drives the IdEditorInput validation, so users got no warning.
Fixes#7139
Adds ten `#[ignore]`'d integration tests in
backend/windmill-runtime-nativets/src/smoke_tests.rs exercising the JS
API surface that the existing nativets tests in tests/worker.rs don't
reach. Run them when bumping the deno_core / deno_ast / deno_runtime /
swc_* pins in backend/Cargo.toml, not on every CI:
cargo test -p windmill-runtime-nativets smoke -- --ignored
# skip network-dependent ones with `--skip smoke_net_`
Why here and not in tests/worker.rs:
windmill-runtime-nativets is the only consumer of the deno_core crate
in the workspace — ScriptLang::Deno spawns the external `deno` binary
via deno_executor.rs, while ScriptLang::Nativets is the only path that
loads the in-process V8 runtime. So a deno_core / deno_ast bump can
only break things downstream of this crate. Co-locating the smoke
tests with the runtime they exercise means they hit the right surface
directly, skip the entire job-queue / worker / API-server stack, and
run in <1s end-to-end (vs. ~30-60s per test for the worker-level
nativets tests).
The tests use the existing `PrewarmedIsolate::spawn` API (already
public for the dedicated-worker path), which gives a clean
"compile-TS → load module → execute main(args) → return JSON" entry
point with no DB or queue plumbing required.
Coverage:
- smoke_basic_value_passing — args binding + return marshaling
- smoke_transpile_enum_and_union — TS-specific syntax (enums,
discriminated unions, casts) through swc_ecma_parser / swc_ecma_ast
- smoke_set_timeout_and_promise_all — deno_web timer ops + V8
microtask drain order
- smoke_url_and_searchparams — deno_url surface
- smoke_web_blob_btoa_atob — deno_web Blob + base64 ops
- smoke_large_payload_roundtrip — 512 KB string in/out through the
op-table boundary
- smoke_error_propagation_with_message — thrown Error must surface
in PrewarmedResult::Err with original message
- smoke_concurrent_isolates — 8 isolates spawned in parallel from
the same tokio runtime; catches V8 isolate-setup races
- smoke_net_fetch_example_com — deno_fetch end-to-end against
example.com
- smoke_net_fetch_json_and_headers — deno_fetch with custom request
headers + Response.json() against httpbin.org/anything
`structuredClone` is not currently wired into the nativets global —
documented in the smoke_web_blob_btoa_atob test in case that ever
changes.
All ten tests pass locally against the current pinned versions
(deno_core 0.336.0 / deno_ast =0.44.0 / swc_common =0.37.5).
* feat(operators): allow operators to access assets page
Adds the "assets" key to workspace operator_settings (defaulting to true
for existing and new workspaces) and toggles the frontend default so the
assets page is visible to operators by default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nit: remove settings btn when not available
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: refine windmill ai refactor plan
* refactor: move ai sse plumbing to windmill-ai
* refactor: remove ai re-export shims
* fix: update ee ai memory ref
* chore: update ee-repo-ref to d3bc7fa85195b46b7a38d43c2f806520bf8b5454
This commit updates the EE repository reference after PR #560 was merged in windmill-ee-private.
Previous ee-repo-ref: ff35bf7cc198e13884b33654e1d6dbd8a8b314d3
New ee-repo-ref: d3bc7fa85195b46b7a38d43c2f806520bf8b5454
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(frontend): enable move button when only folder changes
* fix(frontend): mark Path dirty when folder picker changes selection
* fix(frontend): preserve script auto-derive for new items in Path dirty effect
* fix: capture linked variables in trash on bulk resource delete
delete_resources_bulk grew linked-variable cascade deletion in an
earlier commit on this branch but only mirrored the deletion side of
delete_resource — not the trashbin capture side. Linked variables
deleted via bulk were permanently lost while their single-delete
counterparts could be recovered from trash.
Fetch each resource's linked variable rows as JSON before bulk delete
and stash them under `trash_data['linked_variables']` of that
resource's trash entry, matching the shape produced by single-resource
delete.
* fix: ws_specific cleanup gaps in variable rename + bulk delete; tooltip
Four spots:
1. update_variable rename block: when a variable is renamed and a
linked resource at the same path is renamed alongside, also move
any explicit ws_specific 'resource' marker from the old path to
the new one. Symmetric with what update_resource already does for
ws_specific 'variable'.
2. delete_variables_bulk: clean ws_specific 'resource' rows for any
linked resource paths before the resource DELETE. Without this,
bulk-delete leaves orphaned markers that would cause a freshly
recreated resource at the same path to be falsely treated as
workspace-specific. (linked_resource trash capture is already
present in the bulk path — the reviewer note about that was
inaccurate against the current code.)
3. list_ws_specific: ORDER BY item_kind, path so the CLI sees a
stable list across pulls/pushes — cheap on a small per-workspace
row set and avoids spurious diffs.
4. VariableForm tooltip: mirror the resource form so users who find a
variable already toggled know it may have been auto-marked by a
workspace-specific resource referencing it, and that disabling
doesn't retroactively un-mark the referencing resource.
* sqlx prepare
- duration/mem-peak labels replaced by Timer/Cpu icons with tooltips
- 'Auto scroll' -> 'auto-scroll' (lowercase, hyphenated, whitespace-nowrap)
- top bar gets overflow-x-auto so it scrolls horizontally instead of pushing the entire log panel into overflow when narrow
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>
The Python per-package dependency cache could persist an incomplete wheel
extraction with `.valid.windmill` set, then propagate that broken artifact
to every worker through the object store. Customer hit this on
argon2-cffi==25.1.0 (missing argon2/_utils.py), and previously on
botocore/httpx (truncated tars). Symptom is a runtime ImportError that
looks like a missing dependency declaration rather than a Windmill bug.
Three changes that together stop the propagation:
1. After `pull_from_tar`, parse the wheel's `<dist-info>/RECORD` and
confirm every listed path exists on disk before writing
`.valid.windmill`. On failure, wipe the directory and fall through
to a fresh local install — the next install also self-heals the
broken object-store entry by pushing a fresh tar.
2. After `uv pip install` succeeds, run the same RECORD check before
queuing the piptar upload or writing `.valid.windmill`. A bad install
never becomes the source of a broken tar in the object store.
3. Finalize the tar (`drop(tar.into_inner()?)`) before reading its bytes
for upload, so we never push an unfinalized archive (no end-of-archive
marker) to the object store.
Verified with a 60-package end-to-end integration test (first-fill →
clear-local-cache → re-pull-from-objectstore → corrupt-objectstore-tar
→ detect-and-self-heal). All 27 packages on the live test pulled cleanly,
and the deliberately corrupted argon2-cffi tar was caught with the exact
expected log line ("wheel RECORD lists files missing on disk: argon2/_utils.py")
and replaced with a fresh tar.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: parse windmill_failure field in job result to tag run as failure
* feat: preserve top-level fields when windmill_failure tags a run as failure
* fix: address review findings on windmill_manual_failure
* refactor: rename windmill_manual_failure to wm_failure and add wm_* aliases
* fix: prefer injected ManualFailure error over sibling name/message in OTel
* 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(native-triggers): serialize Google channel renewal across replicas
`sync_all_triggers` runs every 5 minutes on every windmill-app replica
with no leader election. Multiple replicas were each rotating the
webhook token, creating a new Google watch channel, and racing the
trigger UPDATE — leaving the loser's new token (in `token`) and channel
(in Google) orphaned. Cloud was accumulating ~5 leaked tokens/week
without the silent best-effort `delete_token_by_hash` ever logging a
warning.
Wrap each per-trigger renewal in a transaction and acquire the row with
`SELECT … FOR UPDATE SKIP LOCKED`. Contending replicas skip the row
instead of duplicating the work. The lock spans `rotate_webhook_token`
→ Google API call → `update_native_trigger_service_config` and is only
released on commit. Re-checks `should_renew_channel` after acquiring
the lock so a replica that committed seconds earlier doesn't trigger a
duplicate renewal.
The pattern matches existing batch-cleanup paths in `monitor.rs`
(job-retention sweep) and other `FOR UPDATE SKIP LOCKED` call sites.
Also logs at `debug!` when `delete_token_by_hash` finds no matching row,
so future investigations can distinguish "deleted" from "not found"
without changing the `Ok(false)` contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fixup! fix(native-triggers): serialize Google channel renewal across replicas
* fixup! fix(native-triggers): serialize Google channel renewal across replicas
fixup! fix(native-triggers): serialize Google channel renewal across replicas
Address claude review:
- #5: per-skip log info -> debug (expected outcome under SKIP LOCKED)
- #2: warn moved out of delete_token_by_hash to the call site that knows the
expected state (try_renew_channel_locked); other callers are race-prone and
shouldn't warn
- #3: NULL service_config now warns (anomalous case)
- #4: post-Google-API DB-update + commit failures log distinctly so the
channel-orphan case is grep-able
Plus: add 14d expiry to Google webhook tokens via ServiceName::webhook_token_expiration,
mint fresh ephemeral-webhook-{service}-{rd5} labels at create + rotate so the
existing 'ephemeral-' filter excludes them from user-token email/critical-alert
paths (no filter changes in 3 places). Orphans now self-clean via the existing
expiry sweep in monitor.rs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fixup! fix(native-triggers): serialize Google channel renewal across replicas
fixup! fix(native-triggers): serialize Google channel renewal across replicas
Address second-round review:
- Claude #1 (P2): username_override_from_label now strips the 'ephemeral-'
prefix for ephemeral-webhook-* labels, so created_by stays
webhook-{service}-{rd5} instead of changing to label-ephemeral-webhook-...
(preserves audit/job-list filter compatibility)
- Codex (P2): updated renew_channel doc — labels are no longer copied; rotate
mints fresh ephemeral-webhook-google-{rd5} with 14d expiration
- Claude #3 (optional): test_rotate_webhook_token now asserts the rotated
Google token has an ephemeral-webhook-google-* label and a populated
expiration
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fixup! fix(native-triggers): serialize Google channel renewal across replicas
fixup! fix(native-triggers): serialize Google channel renewal across replicas
Reconsider the previous fixup: stripping the 'ephemeral-' prefix made
created_by no longer match token.label exactly, defeating the linking
purpose. Just allowlist 'ephemeral-webhook-' alongside the other
recognized webhook/email/ws prefixes — created_by becomes
ephemeral-webhook-google-XXXXX, matching token.label exactly. The
'ephemeral-' substring also informs operators that this is a
system-managed auto-expiring token vs a user-managed webhook trigger.
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>
* [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>
Adds three subpath entries to the windmill-components package's `exports`
and `typesVersions` so external consumers (e.g. windmillhub) can import
these components without resorting to private `node_modules` aliases.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: bind MySQL table listing to configured database name
* refactor: drop DATABASE() sentinel and cover single-table fallback
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: unify MySQL schema-resolution branches via explicit_db binding
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: replace SELECT * with explicit column lists
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: update sqlx offline query cache
* chore: update sqlx offline query cache
* chore: update sqlx offline query cache with EE support
* chore: update sqlx offline query cache, no deletions
* chore: update sqlx offline query cache after rebase
* fix: correct column names in explicit script query lists
- concurrency_limit → concurrent_limit (matches DB column name)
- runnable_settings → runnable_settings_handle (matches DB column name)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: add missing delete_after_secs column to script queries
Also add integration test covering all explicit-column export queries.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: add workspace export integration test covering all explicit-column queries
Covers tarball_workspace (folder, script, resource, resource_type, variable,
schedule, usr, group_) and the mcp_oauth_client SELECT query from windmill-mcp.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: add tarball export integration test covering all explicit-column queries
Single test creates one of each entity type and exercises every runtime-checked
explicit-column query in tarball_workspace. Uses archive_type=tar to avoid
zip feature-gate in CI.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: fix stale verification step and CI contradiction in update-sqlx skill
- Regenerate current_files.txt after EE cache restoration so step 4 reports accurate diff
- Scope "Never use SQLX_OFFLINE=true" to local prepare (CI legitimately uses it)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: remove Co-Authored-By from commit skill template
* refactor: extract SCRIPT_COLUMNS const to single source of truth
Replaces 5 duplicated 44-column lists with a shared const in windmill-types.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* docs: add SAFETY comments to all dynamic SQL call sites
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: address review feedback on SAFETY comments
- Fix missed comment for obo_triggers loop in offboarding.rs
- Fix variable name in comment (table -> table_name) in offboarding.rs
- Fix api-settings comment to reference inline VALID_NAME regex, not validate_dbname()
- Add SAFETY comments to batch_execute calls in api-settings
- Fix db.rs comment: PG_SCHEMA is env var, not compile-time constant
- Add doc comments on RunnableSettingsTraitInternal constants
* docs: remove misleading SAFETY comment on static SQL
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix(forks): strip mode/enabled from merge-UI deploy payload
The CompareWorkspaces merge UI deploys a trigger by fetching the full
GET response from the source workspace and spreading it into an
updateXTrigger call on the target. That spread includes `mode` (and the
legacy `enabled`), so a fork→parent (or parent→fork) deploy would
overwrite the target's enabled/disabled state — silently disabling a
parent's trigger when its config is merged from a freshly-cloned fork
(clones are forced `mode='disabled'`). Affects azure/email/gcp/http
whose `update_trigger` SQL writes the `mode` column.
Strip `mode`/`enabled` in `getTriggersDeployData` so the backend's
existing `is_mode_unspecified()` preservation in `update_trigger` keeps
the target row's `mode` untouched. The same preservation already
protects the YAML/CLI round-trip (where the tarball export strips these
fields); this extends the same guarantee to the merge-UI path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(forks): wire Azure and Email triggers through merge UI deploy
CompareWorkspaces.svelte already lists Azure and Email triggers in its
diff (`triggerServices`), but `getTriggersDeployData` and `existsTrigger`
were missing the corresponding branches. Deploying either kind from the
merge UI threw "Unexpected trigger kind". Add the branches with the
same `stripOperationalState` pattern as the rest, and extend the
`triggersKind` whitelist in `checkItemExists`.
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>
* fix: distinguish job-already-completed from execution failure on OTLP span
handle_queued_job's bool return type conflated two distinct Ok(false)
cases: a real race with another worker (Error::AlreadyCompleted) and any
job execution that returned an error via process_result. The outer "job"
span recorded "job already completed by another worker" for both, so
every failed Python/bun/etc. script ended up with that misleading
otel.status_message even though the real error was correctly recorded
on the inner job_postprocessing span.
Replace the bool with a JobOutcome enum (Completed / Failed { description }
/ AlreadyCompleted). Failed carries the truncated error string, so the
outer span's Status.message now reflects the actual cause.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: extract user-facing error from error_value for span description
Cubic flagged that capturing e.to_string() before the match meant
ExitStatus failures (the most common failure mode for script jobs)
ended up with the generic "exit status: …" string rather than the
script error extracted from job logs by extract_error_value.
Move the description capture to after error_value is built and
deserialise it as ErrorMessage to pull out the structured message.
Falls back to "Job failed" if the value isn't shaped that way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: use permissive description extraction for OTLP status message
Cubic flagged that strict ErrorMessage parsing downgraded real failures
to a generic "Job failed" whenever the result wasn't shaped as
{message, name} — e.g. agent-worker's "See logs for more details" raw
string, or runtime-written result.json files with a different shape.
Switch to parsing as serde_json::Value and pulling .message out if it's
a string, falling back to the whole value if it's a bare string. Only
fall back to "Job failed" when neither is available.
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: Ruben Fiszel <ruben@windmill.dev>
* feat: support restart from steps inside BranchOne, ForLoop, Subflow
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: preserve original job kind in nested restart, support expanded subflow steps
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: read selected iteration from graph state for nested ForLoop restart
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: iteration selectors per ForLoop in restart popup, more nested restart tests
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: extract useNestedRestartState composable
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: cover deployed-subflow + FlowDependencies path in nested restart
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update sqlx prepare cache
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: detect BranchOne/ForLoop ancestors inside expanded subflows for nested restart
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: hide restart button for non-restartable steps (parallel containers, untaken branches)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address review feedback on nested restart PR
- preview FlowRestartButton: hide nested case (chain UUIDs aren't resolvable in
preview path; users can use the run page for nested restart instead)
- branchOneAncestorMatchesOriginal: be permissive when status isn't reachable
(don't hide the button for BranchOnes nested deeper than top-level)
- worker_flow.rs: apply nested_restart_payload swap on the is_simple ForLoop
fast path too, so simple iterations don't bypass restart spawn interception
- FlowStatusViewer: reset expandedSubflows cache on jobId change; drop
$bindable({}) banned pattern for the new prop
- API resolver: validate the leaf step exists before returning (fail-fast)
- doc fix: branch_or_iteration_n is 0-based, not 1-based
- selectedJobStepIsTopLevel reset on early-return in composable
- comment iterationCounts collision caveat
- new HTTP-level integration tests covering the API endpoint contract:
happy path (top-level + nested), unknown step, out-of-range iteration,
parallel-loop rejection
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* revert: remove unreachable nested-restart swap on is_simple ForLoop fast path
The swap is unreachable in valid flows: `is_simple_modules` requires the body
to be a single `script` / `rawscript` / `flowscript` (per `FlowModule::is_simple`),
none of which spawn flow-kind children. Any nested-restart chain targeting a
leaf inside such an iteration is rejected by the API at leaf validation. Even
if a chain reached the worker via `JobPayload::RawFlow.restarted_from`, the
resulting `RestartedFlow` would fail to push (script kind isn't a flow kind).
Replaced the swap with an explanatory comment so the next reader knows why
the symmetry with the non-simple path was deliberately not added.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: handle undefined expandedSubflows + tighten branchOne match check
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: iteration count for restart popup picks up wrong loop on step-id collision
When a top-level ForLoop step shares an id with a loop nested inside an expanded
subflow (e.g. parent has step `e` with 4 iterations and the subflow at step `h`
also has step `e` with 1 iteration), the popup's iteration `<select>` rendered
the subflow's count instead of the parent's because `iterationCounts` was
double-indexed by both the prefixed graph key and the bare leaf segment after
the last colon — last writer wins, and the bare key collided.
- `iterationCounts` now indexed only by the full graph key. The selected-step
iteration picker still works because at the top level the step id IS the
graph key.
- New `nestedPathIterationCounts` keyed by the popup's field-key
('top' / 'inner-N'). Populated by the composable during path construction so
each entry uses the correct prefix-aware graph key, regardless of whether the
ancestor lives in the parent flow or inside an expanded subflow.
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: omit empty assets array on scripts and raw app inline scripts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: skip raw app asset effect on parser errors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: read inline-script tag from app policy in run mode
Previously the worker tag for app inline scripts was taken from the
client-supplied raw_code on every execute. End users running a deployed
app could intercept the request and submit any tag, redirecting the job
to an arbitrary worker group.
Persist the tag on PolicyTriggerableInputs at deploy time, and in run
mode read it from the policy instead of the request body. Preview mode
(editor-only) still honors the client tag, since the editing user is
already trusted by the policy check.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: also reject client tag in legacy run-mode (no app_script id)
The previous commit only enforced policy-tag in the id-bearing arm.
Apps deployed before the lockfile/app_script entry exists hit the
\`(None, Some(raw_code), None)\` arm in run mode (triggerable keyed by
\`rawscript/<sha>\`), where client tag was still trusted.
Hoist an \`is_preview\` flag from the outer match and route both inline
arms through it: client tag is honored only in preview mode.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: support assigning a worker tag to app/raw-app inline scripts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: omit empty tag field from inline script raw_code payload
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* style: shrink tag popover width
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: pair PG arg type with actual Rust binding to keep query_typed_raw safe
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(pg): wrap encoder errors with arg context, add fallback test
Followups on #8999 review:
- Wrap rust-postgres "error serializing parameter N" failures with the arg
name, JSON value kind, and asserted Postgres type plus a hint about an
explicit cast — so users see actionable context instead of an opaque
WrongType.
- Drift-prevention meta-test: assert otyp_to_pg_type and convert_val agree
on the Type for every recognised arg_t when the JSON value matches its
natural Rust kind. Catches future drift if either side changes.
- Integration test for the prepare + query_raw fallback path: confirms
unrecognised arg_t (custom enum) is routed through prepare and the
server-resolved type appears in the failure surface — flips into a
test failure if a regression accidentally routes unrecognised types
through query_typed_raw.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): add otyp_inferred flag + regex-based placeholder renumbering
Two follow-ups from the review of #8999:
1. **Issue #1 (Number/Bool + explicit text decl in WHERE)**
Add `Arg::otyp_inferred: bool` to the parser. The PG SQL parser sets
it `true` only at the "no info → fall back to text" site (bare `$N`,
no inline cast, no `-- $N (TYPE)` decl). All other arg sources keep
it `false`.
In `convert_val` this flag distinguishes:
- explicit text-like target (`-- $1 (text)` or `$1::text`) — coerce
`Bool`/`Number` → `Box<String>` so `WHERE text_col = $1` works
(`text = text` operator). Pre-#8988 behaviour, restored.
- parser-default text (bare `$N`) — bind the value's natural Rust
type so the regression case (`Value::Bool` against a real `bool`
column via `CAST AS bool`) keeps working.
`Arg` is in `windmill-parser`; the new field has `#[serde(default)]`
so persisted signatures stay backward-compatible.
2. **Issue #4 ($5/$50 substring rewrite collision)**
Replace the per-index `String::replace` chain (which turned `$50`
into `$10` when oidx=5 was processed first) with a single regex
pass. `\d+` is greedy, so `$5` and `$50` match as distinct units;
indices outside the mapping are left intact.
3. Tests:
- parser: `test_parse_pgsql_otyp_inferred_flag` covers bare/inline-
cast/decl/mixed shapes.
- executor unit: `convert_val_bool_against_every_arg_t` and
`convert_val_*_number_*` split each text-like target into explicit
vs inferred expectations.
- executor unit: `renumber_sparse_placeholders_no_collision`.
- integration: `test_postgresql_arg_type_combinations` adds 4 cases
covering decl(text)+Number/Bool in WHERE, bare $1+Bool, and
sparse positional args ($5/$50).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg+sdk): enum support, extended String arms, position-aware $N rewrite, SDK quality
Backend:
1. **`AnyTextValue` ToSql/FromSql wrapper**: vanilla `tokio_postgres`'s
`ToSql for String` / `FromSql for String` reject `Kind::Enum` and
`Kind::Domain` even though the wire format is plain UTF-8. The wrapper
accepts those kinds in both directions. End result: explicit
`$1::my_enum` / `CAST($1 AS my_enum)` casts now round-trip without the
ugly `CAST($1::text AS my_enum)` workaround, AND `SELECT enum_col`
results come back as JSON strings instead of erroring at the FromSql
layer.
2. **#10 — Value::String → numeric/real/double/oid/bool**. Without these
arms, a string-encoded value (`"3.14"`, `"true"`) for a non-text /
non-temporal arg_t fell through to `Box<String> + TEXT`, which then
failed at the server (no implicit cast text→numeric in expression
context). Now strings are parsed into the matching native type with
clear error messages on parse failure.
3. **Position-aware `$N` rewrite**: replaces the regex-based renumbering
(which fixed the `$5/$50` substring collision but still walked through
string literals and comments, mangling `'price: $5'` etc.) with a
walk over `parse_pg_statement_arg_positions` — the same
string/comment/dollar-quote-aware tokenizer used for index discovery.
Adds `parse_pg_statement_arg_positions` to the parser's public API.
SDK:
4. **BigInt support**: `JSON.stringify(BigInt)` throws. The SDK now
stringifies bigints before serialisation; the executor accepts
numeric strings into BIGINT arg slots via the existing
`Value::String → INT8` parsing arm. SDK-side `inferSqlType` is split
so `BigInt` always resolves to `BIGINT` (was reaching
`Number.isInteger(BigInt)` which returns false → wrong default).
5. **Homogeneous array auto-tag**: `${[1,2,3]}` against an `int[]` column
now emits `$1::BIGINT[]` instead of `$1::JSON`. Detection covers
primitive types only (number / bigint / string / boolean); mixed or
nested arrays still fall back to JSON. Mixed int/float widens to
`DOUBLE PRECISION[]`.
6. **`.query()` positional bug**: previously the `.query()` method
abused the template-tag builder, which appended `$N::TYPE` after the
user's literal SQL string instead of binding by position
(`SELECT $1, $2` became `SELECT $1, $2$1::BIGINT`). Now `.query()`
builds the executor-shaped content directly: a `-- $N argN (TYPE)`
declaration block followed by the user's SQL verbatim.
Tests:
- Parser: `test_parse_pg_statement_arg_positions_skips_strings_and_comments`
asserts string literals, comments, and dollar-quoted blocks don't
produce positions (so renumbering doesn't mangle them).
- Executor unit: `renumber_sparse_placeholders_no_collision_no_string_mangling`
uses the new position-aware path and includes string-literal + comment
+ `$$…$$` cases. Existing convert_val tests grow to cover new
String→numeric/real/double/oid/bool arms.
- Integration: `test_postgresql_arg_type_combinations` adds 13 cases
(enum round-trip both directions, string→numeric/real/double/bool/oid,
string-literal `$N` non-mangling). The prepare-fallback test now
asserts SUCCESS (not failure) for enum encoding via AnyTextValue.
- SDK: new `typescript-client/tests/sqlUtils.test.ts` (42 tests)
exhaustively covering inferSqlType primitives + arrays,
parseTypeAnnotation, datatable() template tag (with all the new
shapes — BigInt, homogeneous arrays, RawSql, schema preamble),
datatable().query() positional, and ducklake() shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): replace DISCARD ALL with curated reset (preserves typeinfo cache)
Found while exhaustively probing custom-type DX: every cached-connection
reuse was running `DISCARD ALL`, whose included `DEALLOCATE ALL`
deallocates *all* prepared statements server-side — including the typeinfo
statements that tokio_postgres caches per-Client to resolve custom enum /
domain Oids. tokio_postgres still held `Statement` objects whose names
the server had forgotten, so the next custom-type query failed with
intermittent "prepared statement \"sN\" does not exist" errors. The
failure was easy to reproduce: any sequence that forced typeinfo lookup
for two different custom-type kinds on the same cached connection (e.g.
enum followed by domain) would hit it.
Replace `DISCARD ALL` with a curated reset that explicitly targets the
state we actually care about, *without* touching prepared statements:
RESET ALL — GUC parameters (search_path, application
_name, statement_timeout, …)
RESET SESSION AUTHORIZATION — undoes both `SET SESSION AUTHORIZATION`
and `SET ROLE` (RESET ALL does NOT —
these aren't GUC parameters, so without
this an elevated role from a previous
job would silently leak)
UNLISTEN * — drops LISTEN registrations
CLOSE ALL — closes open cursors
Trade-off: temp tables, advisory locks (session-scoped), and user-created
PREPARE statements may persist across cached-connection reuse — rare in
datatable / PG-script workloads. tokio_postgres's typeinfo cache survives
intact, so custom enum / domain queries are fast on subsequent reuse.
Tests:
- `test_postgresql_custom_types_on_cached_connection` — runs 10×
alternating enum + domain queries on a cached connection. Pre-fix this
failed with `prepared statement "sN" does not exist` after the first
reuse; post-fix passes.
- `test_postgresql_set_role_does_not_leak_across_cached_connection` —
switches `SET ROLE` and `SET SESSION AUTHORIZATION` to a non-postgres
role, then runs a follow-up job and asserts current_user/session_user
are restored. Specifically catches the case where someone might switch
back to `RESET ALL` alone (which doesn't cover SET ROLE / SESSION
AUTHORIZATION) and silently introduce a permission-leak vector.
- All existing session-isolation tests
(`test_postgresql_cached_connection_resets_session`,
`test_postgresql_single_worker_session_isolation`,
`test_postgresql_100_jobs_cached`) continue to pass.
Found via end-to-end probing of datatable / PG-script DX, not previously
covered: the existing isolation tests only did `SET ROLE postgres`, the
connecting user, so the leak was invisible.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): address PR #8999 review (cubic + claude)
cubic (P1, real bug):
- `convert_vec_val` for `timetz` array asserted `Type::TIMETZ_ARRAY`, but
chrono `NaiveTime` only encodes for TIME (same caveat as the scalar
arm). Switch to `Type::TIME_ARRAY`; rely on PG's implicit `time→timetz`
assignment cast at the column site. Add an explicit unit test.
claude (#1, silent failure → explicit error):
- `Bool` + explicit `(char)` / `(character)` decl previously silently
bound BOOL, hoping the server would cast at the use site — but PG has
no implicit `bool→char` and the resulting error
("operator does not exist: bool = char") was opaque. Now error at
bind time with an actionable hint to use `bool` decl or pass the
value as a "t"/"f" string.
claude (#2, asymmetry doc):
- Object/Array still coerce to text on `matches!(typ, Typ::Str(_))`
(covers both explicit AND inferred-default text), unlike Bool/Number
which key on `explicit_text_target`. The asymmetry is intentional
(no implicit `jsonb → text` cast in expression context vs PG having
implicit `bool/int → text` casts) — added a body comment so future
maintainers don't try to "align" them.
claude (#3, perf):
- `parse_pg_statement_arg_indices` and `parse_pg_statement_arg_positions`
walked the SQL tokenizer twice. Fold into a single pass that derives
the index set from the position list.
claude (#4, fmt drift):
- `cargo fmt` over the parser crates I touched with perl scripts in the
earlier commit (windmill-parser-{sql,bash,ts,go,php,java,csharp,nu,py,
rust,graphql,yaml,r}). Net cosmetic.
claude (#5, parseTypeAnnotation):
- One-line caveat in the SDK's `parseTypeAnnotation` that the returned
string is presence-only (e.g. `${x}::DOUBLE PRECISION` returns
`"DOUBLE"`, `CAST(${x} AS int)` returns `"int)"` — neither matches a
real PG type, but the only consumer just checks `!== undefined`).
While here — discovered + fixed independently while exhaustively probing
DX:
- **Replace `DISCARD ALL` with curated reset** (`RESET ALL; RESET
SESSION AUTHORIZATION; UNLISTEN *; CLOSE ALL;`). DISCARD's
`DEALLOCATE ALL` killed tokio_postgres' typeinfo cache, producing
intermittent `prepared statement "sN" does not exist` errors on
custom-type queries after cached-conn reuse. New regression tests:
`test_postgresql_custom_types_on_cached_connection` and
`test_postgresql_set_role_does_not_leak_across_cached_connection`
(the latter catches the case where someone might switch back to
`RESET ALL` alone and silently introduce a permission-leak vector —
RESET ALL doesn't cover SET ROLE / SET SESSION AUTHORIZATION).
- **ISO-8601 timestamp results** (`pg_cell_to_json_value`). Pre-fix
`TIMESTAMP` was rendered with a space separator ("2024-01-15 10:30:00")
and `TIMESTAMPTZ` with " UTC" suffix ("2024-01-15 10:30:00 UTC") —
neither parseable by `date-fns parseISO`, JavaScript `new Date()` is
lenient enough to handle them but several frontend `App*Input.svelte`
components use parseISO and fail silently. Switched to ISO-8601 with
`T` separator and `+00:00` offset; arg-parsing path still accepts the
legacy " UTC" suffix for back-compat.
Test coverage:
- 17/17 unit (`pg_executor::tests`)
- 9/9 integration (`backend/tests/worker.rs`, `test_postgresql_*`)
- 27/27 parser (`windmill-parser-sql`)
- 42/42 SDK (`typescript-client/tests/sqlUtils.test.ts`)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): bounded one-shot warning on numeric precision loss + ISO-8601 + NaN handling
Found while probing PG-script DX with millions of numeric cells:
1. **Numeric precision-loss warning**: `numeric` results are still serialised
as JSON Number (back-compat — switching to JSON String would silently
break user code doing arithmetic on results), but we now detect
`Decimal -> f64 -> Decimal` round-trip failure and emit a single
job-log warning recommending a `::text` cast in the SQL. Bounded by
`NUMERIC_PRECISION_CHECK_BUDGET = 256` cells per query (one atomic
load + one fetch_sub on the hot path; first lossy value
short-circuits to a single load thereafter). Worst-case overhead on
a 1M-cell numeric-heavy query: ~25µs of checks + 5ns × N atomic
loads (vs. ~100ms unbounded).
2. **ISO-8601 timestamps**: `pg_cell_to_json_value` previously returned
`"2024-01-15 10:30:00"` (TIMESTAMP) and `"2024-01-15 10:30:00 UTC"`
(TIMESTAMPTZ) — neither parseable by date-fns `parseISO`, which is
what the apps `App*Input.svelte` components use, so timestamp values
silently failed to round-trip into date pickers. Switch to ISO-8601
(`T` separator + `+00:00` offset) on the result side; arg-parser
continues to accept the legacy `" UTC"`-suffixed format for
back-compat.
3. **Float NaN / Infinity results**: `Number::from_f64` returns None for
NaN / ±Inf, which `pg_cell_to_json_value` was raising as
"invalid json-float" — failing the *entire* query if any cell held
one of these special values. Now serialise them as JSON strings
("NaN", "Infinity", "-Infinity") and let the rest of the row come
through. Arg-side: `s.parse::<f64>()` already accepts the same
strings.
Tests:
- `decimal_fits_f64_losslessly_predicate` — covers fits / doesn't-fit
cases for the precision-loss predicate.
- `precision_check_budget_caps_per_query_overhead` — locks in the
budget cap and the loss-flag short-circuit.
- All 9 PG integration tests + 17 unit tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): add pg_advisory_unlock_all to reset; warn on missing args; honor decl defaults
While probing PG-script DX further found three more frictions:
1. **Advisory lock leak** (cubic P2): switching from `DISCARD ALL` to
`RESET ALL; RESET SESSION AUTHORIZATION; UNLISTEN *; CLOSE ALL;`
meant session-scoped advisory locks (`pg_advisory_lock`) leaked
across cached-connection reuse. Add `SELECT pg_advisory_unlock_all()`
to the chain — `DISCARD ALL` covered this implicitly via
`DISCARD PLANS / DEALLOCATE / pg_advisory_unlock_all` and we lost it
in the switch.
2. **Missing-arg silent NULL**: an arg declared in the SQL (e.g.
`-- $1 amount (numeric)`) but not provided in the args object was
bound as NULL with no error / warning. Misspelling the key in the
args object silently produced a row of NULLs — a notorious DX
debugging trap. Now: collect the names of declared-but-missing
args during dispatch and emit a single one-shot warning to the job
logs at end-of-query naming each one. Bound NULL is preserved for
back-compat.
3. **Declaration defaults ignored**: `-- $1 a (int) = 5` carries
`arg.default = Some(Number(5))`, but the dispatch fell straight to
NULL when the arg was missing. Now: respect the default —
user-supplied value > declaration default > NULL. Also fixes the
warning logic above (only warn for args that *don't* have a default).
Tests: existing 19 unit + 9 integration pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): multi-word PG types with [] suffix lost the array-ness; array arms accept stringified values
Two more frictions found while probing SDK end-to-end against a real
datatable resource:
1. **Multi-word array types lose the [] suffix in the parser**.
`transform_types_with_spaces` recognises aliases for "double
precision", "character varying", "timestamp with time zone", etc.
but its return type was `&'a str` — only the bare alias, never with
a trailing `[]`. The `RE_CODE_PGSQL` regex's `\w+` captures stop at
the first space, so the regex's own `(?:\[\])?` array-suffix branch
sees only `"double"` (not `"double precision[]"`); the `[]` was
silently lost. Result: `$1::double precision[]` (which the SDK now
emits for homogeneous float arrays via the new auto-tag) routed
through `Value::Array → Type::JSONB` and the server failed with
"cannot cast type jsonb to double precision[]".
Fix: switch `transform_types_with_spaces` to return `Cow<'a, str>`
and re-check the trailing bytes after a multi-word match. If they
start with `[]`, return `format!("{alias}[]")` — Owned. Single-word
types and the no-match path keep returning Borrowed slices, so no
allocation in the hot path.
2. **Array arms in `convert_vec_val` rejected stringified values for
numeric / int* / bool / oid / real / double**. The scalar `convert_val`
already parses strings into the matching native type for these arg_ts,
but the array variant only accepted JSON-native counterparts. Sending
`["1.5", "2.5", "3.5"]` against `$1::numeric[]` (e.g. via `unnest` for
bulk loading, or `JSON.stringify(BigInt[])` round-trip) failed with
"Mixed types in array". Now the array arms mirror the scalar ones —
`as_<native>().or_else(|| as_str().and_then(parse))` — so both shapes
round-trip cleanly.
Tests: 19 unit + 9 integration pass; existing parser tests cover the
multi-word array forms (the regex-cap behaviour didn't break for
single-word types, and Cow plumbing is transparent to all callers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(parsers): add otyp_inferred field to Arg literals in tests + 3 missed src files
CI failures: the perl-driven sweep that added `otyp_inferred: false` to
every `Arg { ... }` literal when I introduced the field in the parser
schema covered `src/lib.rs` files but missed:
- parsers/windmill-parser-bash/src/lib.rs (mass-edited but a
later format pass un-applied a few sites)
- parsers/windmill-parser-go/src/lib.rs (same)
- parsers/windmill-parser-graphql/src/lib.rs (same)
- parsers/windmill-parser-nu/tests/tests.rs (test file — not
swept the first time)
- parsers/windmill-parser-ts/tests/tests.rs (test file — same)
Also tightened the regex to handle `oidx: None` without the trailing
comma (some test files had the field as the last initialiser line).
`cargo build --features <CI feature combo> --workspace --all-targets`
is clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sdk): Date → TIMESTAMPTZ; NaN / ±Infinity → string
Two more frictions found while running the actual SDK end-to-end against
a live datatable resource:
1. **JS `Date`** fell into the typeof "object" branch and was tagged
`::JSON`. It worked accidentally for `${date}::timestamptz` via PG's
`json → text → timestamptz` implicit cast chain, but `${date}` against
a `timestamptz` column without a user-supplied cast bound the value
as a JSON string and the comparison `timestamptz = json` failed. Now:
`inferSqlType` recognises `Date` and tags `::TIMESTAMPTZ`;
`serializeArgValue` emits `Date.toISOString()` so the executor's
`Value::String → TIMESTAMPTZ` arm parses it cleanly.
2. **JS `NaN` / `±Infinity`** silently became NULL. `JSON.stringify(NaN)`
returns `"null"` per the JS spec, so the value reached the executor as
JSON null — the SDK's `::DOUBLE PRECISION` tag then bound a NULL
double. Fix: detect non-finite numbers in `serializeArgValue` and
stringify them as `"NaN" / "Infinity" / "-Infinity"`. The executor's
`Value::String → FLOAT8` arm (`f64::from_str`) accepts these literals
directly, and the result-side already renders the values as JSON
strings (matching round-trip).
SDK unit tests grow from 42 → 44 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(pg): integration coverage for multi-word arrays + stringified array elements
Locks in the two array fixes from the previous commit
(`fix(pg): multi-word PG types with [] suffix lost the array-ness`)
with end-to-end cases in `test_postgresql_arg_type_combinations`:
- `double precision[]`, `character varying[]`, `timestamp without time
zone[]` — verifies the parser keeps the `[]` suffix after multi-word
alias resolution.
- `numeric[]` / `int[]` / `bool[]` from stringified primitives — verifies
the array arms of `convert_vec_val` apply the same string-coercion
the scalar arms do.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style: fix indentation drift on otyp_inferred lines
cargo fmt cleanup of leftover indentation where the perl-driven sweep
that introduced the otyp_inferred field landed at the wrong column.
No behaviour change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.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>
tracing-opentelemetry only recognizes otel.status_code and
otel.status_message as fields that map to the OTLP Status proto.
The previously-used otel.status_description fell through to the
generic attribute recorder, leaving Status.message unset and
preventing OTLP consumers from filtering spans on error status.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.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>
* feat: ansible delegate_to_git_repo install_requirements, dynamic fields, --limit
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: include .yaml variants in collections/roles requirements lookup
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <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>
* fix: redact GitHub App tokens and Slack OAuth secret for non-admins
`GET /workspaces/get_settings` returned the full `git_app_installations`
JSONB to any workspace member. That column caches the GitHub App JWT and
installation token used by git-sync; the installation token is refreshed
on every git-sync action and valid for ~55 minutes, so the value sitting
in the DB is essentially always live. Null it out for non-admins,
matching the existing `slack_oauth_client_secret` redaction.
The tarball export's v2 settings format (added in #8935) included
`slack_oauth_client_secret` with no admin gating, regressing the same
redaction. Mirror the admin check there.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: split get_settings into admin-only + public endpoint
Adds `WorkspacePublicSettings` and `GET /workspaces/get_public_settings`,
which returns only fields safe for any workspace member to read
(workspace_id, slack/teams team identity, mute_critical_alerts, deploy_ui,
large_file_storage, datatable). `get_settings` is now admin-only via
`require_admin`.
Migrates frontend callers: every caller that read non-sensitive fields
(deploy_ui on trigger pages, mute_critical_alerts on the root layout, slack
team identity for handler pickers, etc.) now uses `getPublicSettings`. The
admin-managed settings UI, git-sync admin context, operator settings,
checkout polling, and full settings page stay on `getSettings`.
This replaces the field-level redactions added in the previous commit:
the type system itself defines the public surface, so adding a sensitive
column to `workspace_settings` no longer defaults to leaking — it stays
out of `WorkspacePublicSettings` unless explicitly added.
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: sanitize underscores in agent worker suffix
The agent worker token wire format is `jwt_agent_<suffix>_<JWT>`, parsed
server-side with `split_once('_')`. JWTs themselves can contain `_`
(base64url alphabet), so the only sound boundary is "suffix has no `_`".
`instance_name()` is the source of the suffix and previously only
sanitized spaces and `-`. A hostname like `austin_hp` would produce
`worker_suffix=austin_hp-<rand>`, the token `jwt_agent_austin_hp-X_<JWT>`
would split as `("austin", "hp-X_<JWT>")`, and the JWT decoder would
return `InvalidToken` on the garbage second half, surfacing as a 401 on
`/api/agent_workers/update_ping`.
Replace `_` with `-` in the hostname-derived suffix so the parsing
boundary stays unambiguous. Pure source-side fix; no wire format change,
no migration needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: apply underscore->dash before splitting in instance_name
Replace `_` with `-` BEFORE the `split("-").last()` step so underscores
behave the same as dashes (consistent with the split-on-dash idiom) and
the resulting instance_name is a single token. For `austin_hp` this
yields `hp` rather than `austin-hp`. No behavior change for hostnames
without `_`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: strip underscores from hostname instead of replacing with dash
Removing `_` preserves more identifier info than replacing with `-`:
`austin_hp` becomes `austinhp` (single useful token) rather than `hp`
(prefix lost to the dash-split). For k8s-style hostnames that already
contain `-`, the `-` continues to do the splitting and `_` is just a
stray character to strip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes silent timeouts and partial-tree rendering when loading large git
repositories into the in-app viewer (Tony Hoang report: 400+ host_vars
files, 32 roles).
Three coordinated fixes:
1. Frontend (GitRepoViewer.svelte): drop the 60s clone timeout. Long-poll
getJobUpdates until the job completes, with a 30 min hard cap and a
user-cancel button. Stream live job logs into the viewer with a link
to the full job page. After success, verify the
.windmill_clone_complete marker before flipping pathExists, so a
partial S3 directory is no longer rendered as a complete tree.
2. Backend (check_s3_folder_exists, EE): new optional marker_file query
param. When set, the handler short-circuits to head() on the marker
object instead of "any object under prefix exists". The frontend now
always passes marker_file=.windmill_clone_complete.
3. Hub script: cloneRepoToS3forGitRepoViewer points at hub/28216, which
uploads files via a bounded-concurrency pool (16 workers), emits
throttled progress logs, and writes .windmill_clone_complete as its
last action. docs/clone_repo_and_upload_to_instance_storage.bun.ts is
the source for that hub publish; docs/git-repo-viewer-hub-script.md
explains the change.
Also drops three unused legacy hubPaths entries
(cloneRepoToS3forGitRepoViewer_0..2) — none were referenced from
anywhere in the codebase.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Always send queries as unnamed prepared statements (query_typed_raw)
when arg types resolve via otyp_to_pg_type. This eliminates the
intermittent "prepared statement \"sN\" does not exist" error reported
on datatable scripts whose Postgres connection sits behind a
transaction-mode pooler (PgBouncer/Supabase pooler/RDS Proxy), where
prepare and execute can land on different backend connections.
The previous code only used the unnamed-statement path when the parser
detected at least one explicitly typed arg; datatable-generated SQL
with bare $1/$2 (relying on inline ::cast hints) fell back to
prepare + query_raw and accumulated named statements (s0, s1, ...,
s882, ...) on the cached connection, which the pooler then dropped.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add workflow-as-code skill
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: make system prompt freshness self-contained
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "fix: make system prompt freshness self-contained"
This reverts commit 7d2fde9585.
* fix: refresh wac generated guidance
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: add wac cli eval cases
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: align wac prompt imports
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
- Login.svelte: poll whoami after popup opens as a safety net for Safari
ITP — when the popup is opened without a fresh user gesture, cookies
and localStorage can be partitioned, leaving the existing postMessage
/ storage signaling unable to reach the parent. Polling is independent
of partitioning since it runs in the parent's own session. An
oauthFlowDone flag guards the three terminal paths (postMessage,
storage, poll) so onLoginSuccess fires exactly once. Adds compact
"oauth: signaled via {postMessage|storage|poll}" diagnostic logs.
- routes/user/login_callback/[client_name]/+page.svelte: replace `??`
with `||` on the cookie/localStorage fallback. The cookie check
returns a boolean, so `??` never fell through and the localStorage
branch was dead code.
- InstanceSettings.svelte: per-category save/discard for the
Auth/OAuth/SAML tab now sees auto_login_provider and
disable_password_login. getSettingsForCategory was returning only
scimSamlSetting for that tab, leaving the dirty check and per-category
save unable to detect changes to those fields.
- vite.config.js: drop a stale personal dev hostname from allowedHosts.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bump windmill-parser-wasm-ts, -py and -py-imports to 1.693.1 in
the CLI and frontend after publishing the new versions.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The $effect in useNestedRestartState wrote to selectedJobStepIsTopLevel
and then read it back via the early-return guard. In Svelte 5 that read
registers the same $state as a dependency of the effect, so each write
reschedules the effect → infinite loop.
Compute the boolean into a local const, write it once, and use the local
for the early return.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The get_flow_by_path_w_draft endpoint omitted flow.labels from its
SELECT and FlowWDraft struct, so the flow editor received undefined
labels. As a result, the labels input rendered empty even when the
flow had labels saved, and adding a new label overwrote the existing
ones (since the frontend sent only the new label and the update SQL
only preserves labels when the field is null).
Closes#8963
* test: isolate WAC v2 python test from test-thread stack overflow
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* ci: bump RUST_MIN_STACK to 4MB for backend tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: edit scopes on existing API tokens
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address PR review feedback on token scope edit
- add SECURITY DEFINER to notify_token_scopes_change so trigger fires under windmill_user/admin roles (cubic P1)
- drop banned $bindable(default) on optional props (CLAUDE.md): make ScopesPicker.value and EditTokenScopesModal.open required
- detect MCP only when *every* scope starts with mcp: so mixed/null-scope tokens fall back to standard picker without dropping non-mcp scopes
- audit log scope payload via serde_json instead of Rust {:?}
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add workspace-shared ui/ folder reusable across raw apps
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add shared ui/ drawer in raw app editor sidebar
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: forward workspace shared ui/ to raw app editor iframe
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* all
* all
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add delete_after_secs and sensitive_inputs to raw app policy
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: simplify sensitive toggle label
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: use tertiary text for sensitive toggle label
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: unset sensitive field when toggled off
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address PR review feedback
- plumb force_viewer_sensitive_inputs/delete_after_secs so editor preview
matches deployed-mode encryption
- reuse resolve_delete_after_secs helper for consistency with scripts/flows
- log+ignore schedule_job_deletion errors so a failed schedule doesn't
surface as an execute_component failure
- fix text-primay typo in CacheTtlPopup and DeleteAfterUsePopup
- tighten extraFields return type to Partial<Pick<...>>
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: show skipped label on flow progress bar for skipped flows
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: narrow is_skipped via 'in' operator on Job union
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: set OTEL span status on failed jobs and add stderr severity toggle
Record otel.status_code=ERROR and otel.status_description on the job /
job_postprocessing tracing spans when a job fails, so OTel exporters
(Sentry, Honeycomb, Datadog) see the standard span-level failure signal
instead of just the success=false attribute. Description is truncated to
512 chars to keep span payloads bounded.
Add OtelSettings.stderr_default_severity instance setting (error | warn |
info | debug, default error) to let operators downgrade the OTEL severity
used for job stderr output. Python logging routes every record >= WARNING
to stderr, so the historical blind stderr->error mapping produces false
positives for scripts like dlt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref for stderr severity toggle
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: clarify OTEL span description for already-completed jobs
When handle_queued_job returns Ok(false) on Error::AlreadyCompleted
(another worker already finished the job during a race), the span
was labeled "job returned false" which is opaque to operators.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: parse Python logging severity from job stderr
Replace the global stderr_default_severity instance toggle with a
worker-side classifier that recognizes Python's canonical
basicConfig() format (LEVELNAME:logger.name:message) and emits the
corresponding tracing level. Lines that don't match keep the
historical tracing::error! fallback, so genuine failures still
surface and non-Python output is unaffected.
Removes StderrLogSeverity, STDERR_LOG_SEVERITY, and the
otel.stderr_default_severity field; adds
classify_python_logging_line in windmill-common.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: cover classify_python_logging_line + fix truncate_description doc
Add unit tests for the Python stderr-severity classifier and correct
the truncate_description docstring to say "bytes" (the cap is byte-
based, with UTF-8 boundary rounding for safety). Addresses Claude
review feedback on PR #8918.
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: hugocasa <hugo@casademont.ch>
* feat: support S3Object input args in native SQL scripts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: review fixes from local-review
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* update parser
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: support restart from steps inside BranchOne, ForLoop, Subflow
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: preserve original job kind in nested restart, support expanded subflow steps
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: read selected iteration from graph state for nested ForLoop restart
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: iteration selectors per ForLoop in restart popup, more nested restart tests
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: extract useNestedRestartState composable
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: cover deployed-subflow + FlowDependencies path in nested restart
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update sqlx prepare cache
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: detect BranchOne/ForLoop ancestors inside expanded subflows for nested restart
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: hide restart button for non-restartable steps (parallel containers, untaken branches)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address review feedback on nested restart PR
- preview FlowRestartButton: hide nested case (chain UUIDs aren't resolvable in
preview path; users can use the run page for nested restart instead)
- branchOneAncestorMatchesOriginal: be permissive when status isn't reachable
(don't hide the button for BranchOnes nested deeper than top-level)
- worker_flow.rs: apply nested_restart_payload swap on the is_simple ForLoop
fast path too, so simple iterations don't bypass restart spawn interception
- FlowStatusViewer: reset expandedSubflows cache on jobId change; drop
$bindable({}) banned pattern for the new prop
- API resolver: validate the leaf step exists before returning (fail-fast)
- doc fix: branch_or_iteration_n is 0-based, not 1-based
- selectedJobStepIsTopLevel reset on early-return in composable
- comment iterationCounts collision caveat
- new HTTP-level integration tests covering the API endpoint contract:
happy path (top-level + nested), unknown step, out-of-range iteration,
parallel-loop rejection
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* revert: remove unreachable nested-restart swap on is_simple ForLoop fast path
The swap is unreachable in valid flows: `is_simple_modules` requires the body
to be a single `script` / `rawscript` / `flowscript` (per `FlowModule::is_simple`),
none of which spawn flow-kind children. Any nested-restart chain targeting a
leaf inside such an iteration is rejected by the API at leaf validation. Even
if a chain reached the worker via `JobPayload::RawFlow.restarted_from`, the
resulting `RestartedFlow` would fail to push (script kind isn't a flow kind).
Replaced the swap with an explanatory comment so the next reader knows why
the symmetry with the non-simple path was deliberately not added.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: handle undefined expandedSubflows + tighten branchOne match check
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: prevent React app editor from overwriting files on theme switch
The ui_builder iframe src embeds the dark-mode flag, so toggling theme
reloads it. iframeLoaded was sticky-true, so the populate effect didn't
refire and the iframe's default "Hello World" template clobbered the
user's files via its initial setFiles message.
Reset iframeLoaded on darkMode change and suppress inbound setFiles from
the iframe until our files are re-pushed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: cancel suppress timer on rapid theme toggles
On a second theme toggle while the previous reload's clear-timer was
still pending, that timer would fire mid-reload and drop suppression
before the iframe finished booting — letting the iframe's default
template setFiles overwrite the user's files.
Track the timer ID, cancel it whenever we re-assert suppression, and
fold the two 500ms timers into one. Race identified by cubic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): add `wmill flow dev` subcommand with per-flow reverse proxy and launch.json
Also generates .claude/launch.json for existing flow folders during `wmill init`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: responsive dev layout and hide splitter for single-pane views
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: clamp flow graph height between minHeight and maxHeight
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(cli): enhance app new with Claude Desktop integration and better defaults
- Add .claude/launch.json to generated app scaffold for Claude Code preview support
- Add "Open in Claude Desktop?" prompt that creates a CLI session and opens it
in Claude Desktop Code mode via the claude://resume deep link
- Improve default CSS template with body background, system fonts, and padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): handle both .flow and __flow suffixes in wmill dev
The flow detection in loadPaths only checked the configured suffix
(dotted or non-dotted), so users with nonDottedPaths=true who had
.flow folders (or vice versa) would see inline script edits treated
as standalone script changes instead of flow changes.
Now checks both suffix forms everywhere: type classification,
folder path extraction, path stripping, and loadWmPath lookup.
Also adds raw_app launch.json generation to init and sync pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(cli): update generated skills with dev workflow and preview commands
Update cli-commands, write-flow, and raw-app skills to document the new
local dev workflow (wmill dev --path, --proxy-port, .claude/launch.json).
Add wmill script preview and wmill flow preview to all script/flow skills
so agents know how to test without deploying.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): include path in dev URL and use open.default for browser
- Append &path= to the printed/opened URL when --path is specified
- Use open.default(url) instead of open.openApp for more reliable browser opening
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cli): add Claude CLI/Desktop detection hints in wmill flow new
Show contextual instructions for previewing flows based on available tools.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate auto-generated CLI skills for new dev flags
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): handle mixed flow suffixes in dev file watcher
The ignore() function uses isFlowPath() which only checks the configured
suffix (__flow or .flow), causing files in the other variant to be silently
ignored. Bypass the ignore check for any file inside a flow folder and
force flow type detection regardless of suffix configuration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cli): drop default proxy in flow folders, open browser, add --no-browser
Manual `wmill dev` in a flow folder should not implicitly enable the
reverse proxy. Both proxy and legacy modes now open the browser; the
new --no-browser flag opts out. Claude Code launch.json templates pass
--no-browser so the IDE preview doesn't fight a system browser window.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): gate dev broadcasts by --path and push currentLastEdit on connect
When --path (or auto-detected flow path) is set, drop file events for
any other path so the dev page stays locked to the requested resource
and currentLastEdit can never reflect an unrelated edit. The connection
handler proactively pushes currentLastEdit so the page renders without
waiting for the first file change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): prefer WebSocket for flow round-trip when wmill dev is connected
updateFlow used isInIframe priority, which routed Claude Code's iframe
preview through postMessage (no listener) and silently dropped flow
edits. Flip the priority: when the wmill dev WebSocket is open, use it
(covers standalone tabs and Claude Code's preview); fall back to
postMessage only when no WS is connected (the VS Code extension's iframe
URL has no `local=true`, so it never opens one). Also stop assigning
lastSent before a channel actually accepted the message, so a CONNECTING
WS doesn't silently swallow the first change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(dev): url is source of truth for path; add workspace file picker
Drops the server-side --path gate added in 3c2d5155e1. The dev page now
filters by its URL's ?path= and the CLI is a dumb broadcaster, which
lets multiple tabs each watch different paths. When the URL has no
?path=, the page asks the CLI for a list of workspace items (flows,
scripts, raw_apps) via a new {type:'listPaths'} WS message and renders
a picker. Clicking a flow or script soft-updates the URL via
history.pushState and loads it; raw_apps surface a hint to use
`wmill app dev` since they don't render here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(dev): picker uses homepage tree view with summaries
Replace the hand-rolled Button-list picker with a TreeView-style layout
that mirrors the Windmill homepage: folder/user tree grouping via
`groupItems`, item rows rendered through the shared `Row.svelte` (no
actions, no favourites, no link — just the visual), a `SearchItems`
fuzzy filter with the same search input styling and placeholder as the
homepage, and `group-open:` chevron toggling on native <details>.
The CLI's listWorkspacePaths now also reads summaries from each item's
metadata (flow.yaml for flows, <script>.script.yaml for scripts) in
parallel so the picker shows summaries as the primary row label, same
as the homepage. Raw apps have no standard manifest so they show the
path only.
Additional polish: title shows "<workspace> (local)" instead of
generic text, subtitle trimmed, item-wrapper owns the border-b so
Row's internal last:border-b-0 doesn't zero it out, summary border
gated on group-open: to avoid doubled lines when a folder is
collapsed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): wmill dev --no-browser was a no-op
Cliffy's `.option("--no-browser", ...)` creates an option named
`browser` (boolean, default undefined) that becomes `false` when the
flag is passed. The previous code checked `opts.noBrowser`, which
Cliffy never populates, so the guard silently no-op'd and the browser
always opened. Rename to `browser` and check `=== false` explicitly,
matching the `wmill app dev --no-open` convention.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(dev): picker warns when wmill dev server is unreachable
Track WS state in Dev.svelte (connecting/open/closed) — 'closed' is
set on either the WS error or close event. When closed, the picker
replaces the toggle + search + tree with a warning Alert telling the
user to run `wmill dev` from the workspace root. Toggle and search are
hidden rather than rendered disabled because there's nothing to filter
anyway.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): rename wmill dev --no-browser to --no-open
Match the pre-existing `wmill app dev --no-open` flag. Having
`--no-browser` on one dev command and `--no-open` on the other was
just an oversight from my earlier change. All three launch.json
templates (init, flow new, sync pull) switch to `--no-open`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): wmill init creates root .claude/launch.json for the picker
Adds a workspace-root .claude/launch.json so Claude Code can launch
`wmill dev` from the project root and land on the file picker (no
--path → picker mode). Per-flow and per-raw_app launch.json files are
already generated by the existing scans. Skipped (with a gray log) if
the file already exists, so the user's customizations are preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): add skipClaudeAssets wmill.yaml flag
When `skipClaudeAssets: true` is set in wmill.yaml, all generators
that previously wrote Claude-specific assets become no-ops:
- writeAiGuidanceFiles skips CLAUDE.md and .claude/skills/
(AGENTS.md is still written — vendor-neutral)
- wmill init skips the root .claude/launch.json + per-flow +
per-raw_app launch.json scans
- wmill sync pull skips the per-flow + per-raw_app launch.json scans
- wmill flow new skips the new flow's .claude/launch.json
- wmill app new skips the new raw_app's .claude/ folder + launch.json
The flag is added to SyncOptions, DEFAULT_SYNC_OPTIONS, and the
generated wmill.yaml template (commented out — opt-in).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): wmill init removes existing Claude assets when skipClaudeAssets is set
Re-running `wmill init` with `skipClaudeAssets: true` now removes
previously-generated Claude assets so the workspace state matches the
config. Narrow scope, no confirmation:
- per-flow / per-raw_app .claude/launch.json (each parent .claude/
collapsed if empty)
- root .claude/launch.json
- .claude/skills/ (wholly ours; safe to remove the subtree)
- root .claude/ collapsed if empty
- CLAUDE.md only if its content matches the default
("Instructions are in @AGENTS.md\n"); otherwise left in place
with a note
Each removal is logged in yellow under a single gray intro line that
prints lazily on the first removal — a clean tree stays silent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): wmill workspace add browser open silently no-ops
`open.openApp(open.apps.browser, { arguments: [url] })` resolves its
Promise even when the OS-level launch does nothing, so the CLI prints
"Opened browser for you" but no tab appears. Same pattern was already
fixed in `dev.ts` by commit 3272c29c2e — use `open.default(url)`,
which delegates to the native URL opener (`open` on macOS, `xdg-open`
on Linux, `start` on Windows) and actually rejects on failure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): wmill init workspace prompt no longer duplicates active profile name
Cliffy's Select.prompt renders `default: X` as `(X)` next to the
question header, which duplicates whichever workspace name the
default points to. Drop `default` and instead reorder the list so
the active profile is first (cursor-preselected by virtue of position)
and append "— active" to its label so the indicator lives where it's
contextually relevant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(skills): expand preview-vs-run guidance for write-flow + all write-script-* skills
Both `wmill flow preview/run` and `wmill script preview/run` have the
same intent split — preview hits the local file, run hits the deployed
version, sync push deploys. The skills' "after writing" sections used
to terse-list the commands and just say "do not run them yourself",
which encouraged the wrong reflex of `sync push` + `run` to "test".
Rewrite the section in both `system_prompts/base/flow-base.md` (drives
write-flow) and the `script_cli_intro` block in
`system_prompts/generate.py` (drives all write-script-<lang>) to:
- explicitly list `preview` as the default for local iteration,
- spell out the few cases when `run` or `sync push` are appropriate,
- offer to test as a one-sentence next step (no multi-option menus),
- mark `preview` as safe to run autonomously.
Regenerate auto-generated/ + cli/src/guidance/skills.ts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): wmill dev — clearer mode names and accurate startup messaging
- Rename `startLegacyServer` to `startDirectServer`. "Legacy" implied
it was on the way out; the two modes (proxy vs direct WS) actually
serve different topologies and both stay. Add comments above each
section spelling out who they're for: proxy mode for embedders that
require a localhost origin (Claude Code preview), direct mode for
standalone browser tabs and the VS Code extension iframe.
- Replace the stale "Dev server will automatically point to the last
script edited locally" log line. Now print path-aware text:
- with --path (or auto-detected): "Watching <path> — edits will live
-reload in the dev page"
- without: "Open the dev page and pick a flow or script to preview —
edits will live-reload" plus a hint about --path
Mirror the same in proxy mode after the listen callback.
- Drop the redundant "Go to <url>" line when --no-open isn't passed
(maybeOpenBrowser already prints "Opened browser at <url>").
- Rename "Server listening on port 3001" to
"Dev WebSocket listening on ws://localhost:<port>/ws" so the line's
purpose is obvious.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): drop per-folder .claude/launch.json generation
Stop creating `.claude/launch.json` inside every flow folder, raw_app
folder, and at `wmill flow new`/`wmill app new` time. The workspace-
root `.claude/launch.json` from `wmill init` stays — it's the picker
entry point and the one place where the deterministic "click → preview"
UX is high-value.
Removed from:
- `wmill init` — per-flow + per-raw_app scans
- `wmill sync pull` — per-flow + per-raw_app scans (also drops the
now-unused `node:fs` mkdirSync/writeFileSync import)
- `wmill flow new` — bootstrap no longer scaffolds `.claude/`
- `wmill app new` — same; also drops the `.claude/launch.json` lines
from the post-create directory listing
Skills already give the agent the right CLI commands, so per-folder
launch.json was redundant context. Existing files in user projects
keep working but won't be regenerated; `wmill init` with
`skipClaudeAssets: true` cleans them up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): wmill app new flags + tighten raw-app skill for AI agents
`wmill app new` is interactive by default, which hangs forever when an
AI agent tries to use it. Add flags so the wizard can be bypassed
end-to-end:
- `--summary <text>`, `--path <path>`, `--framework <react19|react18|
svelte5|vue>` (required for non-interactive)
- `--datatable <name>` (opt into the datatable wizard)
- `--schema <name>` (creates schema with CREATE SCHEMA IF NOT EXISTS
if it doesn't already exist; only valid with --datatable)
- `--overwrite` (replace existing directory without prompting)
- `--no-open-in-desktop` (suppress the Claude Desktop offer)
Mode is auto-detected: providing any of --summary/--path/--framework
puts the run into non-interactive mode where the datatable wizard,
overwrite prompt, and Claude Desktop prompt all skip silently (or fail
fast on conflict instead of waiting for stdin). Each provided flag is
validated upfront with a clear error message.
Skill side: rewrite `system_prompts/base/raw-app.md`'s "Creating a Raw
App" section so the AI agent knows it should run the command itself
with flags (not tell the user to run it interactively). Direct the
agent to use `AskUserQuestion` with one bundled call to gather any
missing summary/path/framework — refuse to invent values, refuse to
default. Anti-patterns spelled out explicitly.
AGENTS.md template (`cli/src/guidance/core.ts`) had a contradicting
line ("MUST ask the user to run wmill app new in its terminal first")
that was loaded eagerly into agent context and overrode the skill —
replaced with the same agent-driven guidance, pointing to the
raw-app skill for the full procedure.
Regenerate auto-generated/ + cli/src/guidance/skills.ts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): per-target preview launch.json + agent uses wmill flow new
Refactor the agent's dev/preview workflow:
- Drop root .claude/launch.json generation from `wmill init`. Sharing one
generic entry across sessions caused preview collisions; agents now add
per-target named entries (windmill: <wmill_path>) on demand.
- New `preview` skill in system_prompts/base/preview.md. Branches on
whether `mcp__Claude_Preview__*` MCP tools are available: with them,
add a per-target launch.json entry pinning its own port + --proxy-port
+ --path + --no-open and invoke the MCP preview tool; without them,
start `wmill dev --path <X> --no-open` directly and hand the URL the
CLI prints to the user. Never touch launch.json in the direct case.
- Agents must run `wmill flow new <path>` themselves to scaffold flows
(folder + flow.yaml with the right suffix), parallel to the existing
`wmill app new` rule. Missing path/summary trigger AskUserQuestion;
no inventing values.
- write-flow skill: 4-step Creating a Flow procedure that opens the
visual preview *before* editing flow.yaml so the user watches the
flow take shape via live reload.
- `wmill flow new` always prints the `wmill dev --path <X>` preview
hint; drop the Claude CLI/Desktop detection branches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(skills): open app preview before editing in raw-app skill
Mirrors the flow skill's Step 3 — opening `wmill app dev` via the
preview skill before touching App.tsx so the user watches the app
take shape via live reload, instead of seeing the finished result
at the end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(dev): guard WS replaceFlow with lockChanges to prevent echo
The postMessage handler at Dev.svelte:306-312 wraps replaceFlow with
lockChanges = true (cleared 500 ms later) so the $effect on
flowStore.val doesn't immediately re-serialize and re-send the freshly
received payload. The WebSocket handler did not, so on the initial
flow push (dev.ts:568-574 sends currentLastEdit on connect), the
client would echo back to handleFlowRoundTrip, which runs the
orphan-file scan. On content equality the write was a no-op, but the
scan could still delete files the server did not list.
Mirror the same lockChanges/timeout pattern in the WS replaceData
handler. Apply to both flow and script paths for symmetry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): correct wmill dev description + gate broadcasts server-side
Two related fixes:
1. The 'auto-pushes them to the remote workspace' wording in the
wmill dev description was wrong — the command never deploys, it
only broadcasts file changes over WS for live preview. Reworded
to call this out explicitly and point at 'wmill sync push' for
the deploy case.
2. Move the path filter out of the client (Dev.svelte:491-495) and
into broadcastChanges. Earlier the filter was client-side with
the comment 'server stays a dumb broadcaster' even though commit
3c2d5155 was titled 'gate dev broadcasts by --path'. Doing the
compare server-side aligns the implementation with the commit
narrative, cuts WS traffic when --path is set, and keeps the
per-tab semantics for the picker (each picker tab still gets the
full 'paths' listing on first connect).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): drop dead launch.json cleanup, fix description, narrow orphan scope
Three review fixes:
1. cleanupClaudeAssets removed both root and per-folder
.claude/launch.json files that this CLI never generates anymore.
Per the user's "feature hasn't been released yet" guidance, no
migration is needed — drop the dead scan and the root rm. Also
drop the now-unused nonDottedPaths argument (and its flowSuffix
/ rawAppSuffix locals).
2. The skipClaudeAssets description in template.ts listed
.claude/launch.json among the assets it skips, but launch.json
is no longer generated. Drop it from the description string.
3. The dev round-trip's orphan cleanup deleted any non-dot file in
a flow folder that wasn't in extractedPaths — including
README.md, fixtures, TODO.md, etc. Restrict the deletion to
files whose extension is in a known inline-script set
(.ts/.js/.py/.go/.sh/.sql/.ps1/.php/.rs/.java/.cs/.r/.graphql).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli, frontend): dedupe flow suffix helpers, use UI components
Five small follow-ups from the PR review:
1. dev.ts already had stripFolderSuffix() but three callsites were
reimplementing the same .flow/__flow if-else inline. Add an
isFlowFolderName(name) helper next to it and replace the duplicates
in startProxyServer's cwd check, the file-watcher localPath strip,
and normalizeWmPath.
2. Dev.svelte:866 was a <div onclick> with two svelte-ignore comments
for the missing a11y handlers. Replace with a real <button
type="button"> — kills the warnings, no visual change.
3. Dev.svelte:1283 was a raw <input type="text"> for the module
summary. Replace with the existing <TextInput> component (same one
the picker search at :1010 uses), per frontend/CLAUDE.md.
4. Dev.svelte:197 typed relativePaths as any[]; tighten to the actual
union (string | [number, string])[] — the python helper returns
tuples, the typescript one returns strings.
5. app/new.ts:822 fired exec("open <deeplink>") with no callback, so
an OS that refused the URL scheme silently failed and we still
logged "Opened in Claude Desktop!". Move the success log inside an
exec callback that surfaces the error and prints the deep link for
manual opening.
Plus a brief comment above parseWatchPath explaining its resync
contract (initial load + popstate + explicit pickPath, no generic
pushState listener).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): regenerate auto-gen for dev description; drop apostrophe to satisfy parser
generate.py:326 extracts .description() with the regex
[^"\']+ which bails on either quote type. Commit ff3a8e4ebd's new
description had an apostrophe inside double quotes ('wmill sync
push'), so the parser saw no description at all and the
auto-generated files dropped the line entirely — which is what
check-freshness caught on origin/main.
Quickest path to green CI: rephrase the description without the
inner apostrophe, then regenerate. The generator's regex is the
real bug but fixing it is out of scope here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): seed app .claude/launch.json before opening Claude Desktop
When the user accepts "Open in Claude Desktop?" in wmill app new, write
a per-app .claude/launch.json (named "windmill: <appPath>") into the
freshly-created app folder before the deep link fires. Entry runs
'wmill app dev --no-open --port ${PORT:-4001}' from the app folder
(which is the cwd Claude Desktop opens with), so the user can hit play
right away to launch the preview.
Skip if .claude/launch.json already exists — never clobber user edits.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix toggles positions
* fix(dev): gate picker mode on ?local= so VS Code iframe still renders content
The VS Code extension iframe loads the dev page without ?path= and
without ?local=true. After the picker rework, an empty watchPath
flipped pickerMode on, so the page rendered the picker UI even
though the extension was sending replaceScript / replaceFlow
postMessages — leaving the user stuck on the picker forever.
Picker mode only makes sense on the local dev page, where the wmill
dev WebSocket can supply the workspace listing. Anywhere else (VS
Code iframe, plain remote tab) the picker has no data source and no
purpose. Add an isLocalDevPage check so the picker only shows when
?local=true is present.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(dev): mirror vscode extension's processFlowMessage round-trip
Three changes that bring our wmill dev round-trip into lockstep with
the windmill-vscode extension's processFlowMessage in src/extension.ts:
1. New cli/src/commands/dev/pathscript-restore.ts — verbatim port of
the extension's src/utils/pathscript-restore.ts. Adds AI-agent tool
walking that the previous local copy was missing (flows with
PathScript-shaped tools weren't being preserved across round-trip).
Header comment makes the cross-repo link explicit.
2. handleFlowRoundTrip rewritten to mirror processFlowMessage step-
for-step: reads failure_module + preprocessor_module from the
current flow.yaml, passes them to extractCurrentMapping, shares one
pathAssigner across all extraction calls, extracts inline scripts
from those special modules too, skips writing files whose content
starts with !inline (treats as pointer directives), and only
rewrites flow.yaml when the serialized YAML actually differs.
3. snapshotPathScripts / tagReplacedPathScripts callsites in loadPaths
were passing the FlowFile wrapper instead of FlowFile.value — the
helpers walk .modules / .failure_module / .preprocessor_module,
which only exist on .value, so PathScript snapshots silently
no-op'd on the file-watcher path. Pass .value at all four sites.
Deliberate divergence from the extension: orphan-cleanup keeps the
INLINE_SCRIPT_EXTS allow-list so README.md / fixtures aren't deleted.
The extension's version still over-deletes; that's tracked separately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(skills): offer visual preview after create instead of auto-opening
Both write-flow and raw-app skills used to instruct the agent to open
the visual preview without asking right after wmill flow new /
wmill app new, on the rationale that live reload is most useful when
the page is already up. In practice this surprised users — opening
the dev page has side effects (browser window pop, possibly a
launch.json entry under MCP-preview Branch A) that warrant consent.
Change Step 3 in both skills from "open it without asking" to "offer
it as a one-sentence next step" — same pattern the same skills
already use for programmatic wmill flow preview offers. Two then-
necessary anti-patterns ("just open it", "open it before editing")
are dropped along with the auto-open instruction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): probe both ip stacks before binding wmill dev proxy / app dev port
Node's default listen() has platform-dependent dual-stack behaviour.
If the requested port is already held on the IPv4 stack, listen() can
silently fall back to binding IPv6-only ([::1]:N). The OS then routes
new localhost connections to the older IPv4 listener, so the user
opens http://localhost:N and sees a stale prior server with no signal
that anything is wrong. Bit us in practice: a leftover wmill dev
--proxy-port 4000 served traffic for a freshly-started wmill app dev
--port 4000.
New helper at cli/src/utils/port-probe.ts probes both 0.0.0.0 and ::
before binding. On collision it walks upward to the next free port
(up to +20) and logs a prominent warning naming the holder when lsof
/ ss can find it:
Port 4000 is already in use (held by PID 91418 `bun`). Using
port 4001 instead.
Wired into:
- wmill dev --proxy-port: the resolved port flows into both
proxyServer.listen() and the &port=N parameter in the redirect
URL, so they always match. Bind explicitly to 0.0.0.0.
- wmill app dev --port: only when the user passed --port explicitly
(the default getPort.default(...) path already handles fallback).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(dev): pass placeholder via TextInput inputProps not as top-level prop
`<TextInput>`'s top-level Props don't include `placeholder` — native
input attributes go through the `inputProps` field. The previous
`<TextInput placeholder="Summary" .../>` failed `npm run check` with
"Object literal may only specify known properties, and
'\"placeholder\"' does not exist in type 'Props<\"input\">'.".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nit
* fix(cli): sequential port probe + sync dev test regex with renamed log
Two CI regressions on test-linux:
1. port-probe parallel race on Linux. isPortFreeOnBothStacks ran the
IPv4 and IPv6 binds via Promise.all. On Linux the default is
net.ipv6.bindv6only=0, so a bind(::, port) socket also takes the
IPv4 stack on the same port. Concurrent v4 + v6 binds then race for
v4 — one wins, the other gets EADDRINUSE on a port that is actually
free. Walks 20 ports up, all fail the same way, throws, child exits.
Tests that fetch http://localhost:port time out at 60s.
Doesn't repro on macOS (bindv6only=1 by default — what I tested
against). Probe sequentially so each bind fully releases before the
next starts.
2. dev_server.test 1 regex out of sync. Commit 018dc3861a renamed the
startup log from "Server listening on port N" to "Dev WebSocket
listening on ws://localhost:N/ws" but didn't update the test, which
times out at 30s waiting for the old string. Update the regex to
match the current log.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update system_prompts/auto-generated/skills/write-script-graphql/SKILL.md
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* fix(cli): address dev/app PR review — bugs 1-7
Per code review:
1. app new.ts — wrap claude --session-id exec in try/finally so the
spinner setInterval is always cleared. On rejection control jumped
to the outer catch and the spinner kept writing \r forever, garbling
subsequent output.
2. app new.ts — make --overwrite actually wipe the dir before
re-creating. Previously logged "Overwriting" but only skipped the
prompt; leftover files from a different framework (e.g. App.tsx
from a prior react18 install when re-scaffolding as svelte5)
survived and produced a hybrid scaffold.
3. dev/dev.ts — anchor the flow-folder match on path segments. The
substring checks (cpath.includes(".flow/") / "__flow/") also fired
on names like notes_about__flow_design/readme.md. New
isInsideFlowFolder + findFlowFolderPrefix split on "/" and check
segment suffixes. Drops the now-unreachable script→flow fallback
inside the else branch.
4. dev/dev.ts — direct mode also routes through resolveBindPort so it
detects dual-stack collisions like the proxy mode does. Bare getPort
only probes one stack, defeating the whole point of port-probe.ts.
Also bind to BIND_HOST explicitly. Drops the unused getPort import.
5. dev/dev.ts — normalize opts.path once after mergeConfigWithConfigFile.
broadcastChanges compared against a non-normalized opts.path, so
--path f/foo/ or --path f/foo.flow silently dropped every broadcast.
Also pulls normalizeWmPath to module scope (was a closure inside dev()).
6. dev/dev.ts — guard the initial-state ws.send with readyState === OPEN,
matching the other branches' pattern.
7. dev/dev.ts — typo: "givena" → "given a".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): address dev/app PR review — items 8-10
8. dev/dev.ts — derive INLINE_SCRIPT_EXTS from exts so adding a new
script language to script.ts auto-extends orphan cleanup. Previously
.gql, .nu, .rb were missing — flows using those languages would
leave orphaned inline files behind. Excludes .yml because user
fixtures commonly use it in flow folders, and leaving a stale
.playbook.yml inline script is preferable to deleting a fixture.
Keeps .js for hand-written flows that aren't in the exts list.
9. app/new.ts — wrap Claude Desktop install probe + prompt in
process.platform === "darwin". The probe (ls /Applications/Claude.app)
and the open command both only work on macOS — the explicit guard
makes the platform scope grep-able.
10. app/new.ts — switch the deep-link spawn from exec(`open ${shell-
escaped url}`) to execFile("open", [deepLink]). sessionId is a UUID
and absAppDir is URI-encoded today so the old form was safe, but
execFile removes the shell entirely.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli,dev): cubic review — port-probe error semantics, pluralize spacing
[2] cli/src/utils/port-probe.ts — distinguish IPv6-unsupported from port
collision in isPortFree. Previously every error code returned false,
including EAFNOSUPPORT / EADDRNOTAVAIL on the IPv6 probe when the host
has no v6 stack at all (IPv4-only containers). resolveBindPort would
then walk all 20 ports getting the same error and throw. Treat only
EADDRINUSE / EACCES as "not free"; everything else as free.
[13] cli/src/commands/app/dev.ts — only probe both stacks when binding
to localhost. The dual-stack collision risk is specific to localhost
(which resolves to 127.0.0.1 + ::1); for an explicit IPv4 host there's
only one stack to worry about, so don't move the user's requested port
over a phantom v6 collision.
[14] frontend/src/lib/components/Dev.svelte — pluralize already inserts
a space between quantity and word, so " item" produced "3 items".
Drop the leading space in both call sites.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(system_prompts): cubic review — preview args, skill scopes
Source changes in base/ + generate.py, then regenerated auto-generated/
via python system_prompts/generate.py. Per cubic review:
[4]+[7] generate.py — "pick plausible args from the `main` signature"
was language-blind. SQL queries and Bash scripts use $1/$2 positional
parameters, not a main(...) signature. Reword to call out both shapes
explicitly so the wording survives across all 19 generated language
skills (postgresql, bash, mysql, …) instead of just the ones that
happen to have main().
[5] base/raw-app.md — the "CLI Commands" table said "Tell the user
they can run these commands (do NOT run them yourself)" while the
"Creating a Raw App" section above (added in this PR) tells the agent
to run `wmill app new` itself. Carve `wmill app new` out of the table
and add a one-line note pointing back to the create flow, so the
guidance no longer self-contradicts.
[10] base/preview.md — "These print a `Go to <url>` line on stdout"
was wrong for `wmill app dev`, which prints
"🚀 Dev server running at <url>". List both line shapes explicitly and
suggest a loose http:// match for URL capture.
[12] base/flow-base.md — "regenerate lock files for the flow you
modified" misstated the default scope. `wmill generate-metadata`
scans scripts, flows, and apps by default
(see cli/src/commands/generate-metadata/generate-metadata.ts:71-73).
Update wording to call out the default scope and how to narrow it.
Also folds the cubic [1] graphql safety wording (originally a one-off
edit on the auto-generated file in a895db7) back into generate.py
itself, so it survives regeneration and applies to all language skills.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(system_prompts): cubic round 2 — language-specific placeholder syntax
Round 1 wording was too narrow:
- "$1, $2 placeholders for SQL queries and Bash" was wrong for MySQL
(`?`), Snowflake (`?`), MSSQL (`@P1`), BigQuery (`@name`), and
PowerShell (which uses `param(...)`, not main()).
- The preview-skill URL match said "first `http://...` token" — remote
workspaces serve HTTPS, so the regex would miss them.
Source-only fixes in generate.py and base/preview.md, then regenerated
auto-generated/ via python system_prompts/generate.py.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(flow): track maxHeight in FlowGraphV2 height effect
cubic [3]: updateHeight() reads both minHeight and maxHeight, but the
$effect only tracked minHeight. Changing maxHeight alone (e.g. when a
parent shrinks the cap during a layout transition) left height frozen
at the previously clamped value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(system_prompts): tool-agnostic wording in base/
cubic [11]: system_prompts/README.md says these prompts must NOT
contain tool usage instructions. Three base files violated this:
- base/flow-base.md (4× AskUserQuestion). Worst offender — leaks into
the frontend copilot via FLOW_BASE in prompts.ts (consumed by
getFlowPrompt in frontend/src/lib/components/copilot/chat/flow/
core.ts:1287). Frontend has no AskUserQuestion tool, so the wording
was both irrelevant and confusing there.
- base/raw-app.md (5× AskUserQuestion + 1× mcp__Claude_Preview__).
CLI-skill-only but covered by the same scope rule.
- base/preview.md (5× mcp__Claude_Preview__). CLI-skill-only, same.
Replaced with role descriptions: "ask the user (use a structured-
question tool if your runtime has one)" and "a tool that can embed a
localhost URL inside the IDE / chat surface". Kept one mention of
mcp__Claude_Preview__ in preview.md as an illustrative example, since
documentation of one runtime is fine — what's not fine is gating
behaviour on a specific tool name.
Source-only edits, then regenerated auto-generated/ via
python system_prompts/generate.py.
Verification: grep -r AskUserQuestion system_prompts/auto-generated/
now returns nothing. The remaining AskUserQuestion refs in
cli/src/guidance/core.ts are hand-written CLI-only AGENTS.md content
(not part of system_prompts), and Claude Code does have that tool, so
those are correctly scoped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(flow): drop .no-splitter CSS hack, use snippets to skip Splitpanes
cubic [8]: the previous fix for "top pane is empty in aiagent / noEditor
mode" was a CSS rule that hid `:global(.splitpanes__splitter)` inside
.no-splitter. That cascaded into nested splitpanes too — the aiagent
left/right tabs panel (line 1043), the debug-console editor split
(line 877), and the doubly-nested debug panel (line 1472) all lost
their resize handles.
Refactor the layout instead. Extract top-pane and bottom-pane content
as snippets, then conditionally render either:
- just the bottom snippet (no Splitpanes wrapper) when the top pane
would be empty (aiagent or noEditor), or
- the original two-Pane Splitpanes layout otherwise.
This removes the splitter at its root rather than hiding it, so
nested splitters are unaffected. The bottom Pane's complex bind:size
getter/setter (which returned 100 when aiagent) collapses to a simple
binding now that the aiagent path no longer goes through the wrapping
Pane at all.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nit
* fix(flow,preview): cubic round 3 — FlowPathViewer regression + preview skill rewrite
[3149192182] FlowModuleComponent.svelte: my last refactor's
"aiagent || noEditor" condition stripped the FlowPathViewer for
noEditor + type === 'flow', because the top-pane snippet was no longer
rendered. The flow-viewer pane is the only thing that *does* show in
that mode, so it shouldn't have been collapsed. Tighten the condition
to "aiagent || (noEditor && type !== 'flow')".
[3149060930] system_prompts/base/preview.md: Branch A detection was
too broad — "can embed or open a localhost URL" is strictly weaker
than "can read .claude/launch.json and launch a configuration". Only
the Claude Desktop / Code MCP integration does the latter; most
embedders only do the former. Restructure preview.md around two
orthogonal axes:
1. Mode (proxy vs direct) — driven by "does the embedder need a
localhost URL?". Direct is the default; proxy is for embedders
that sandbox cross-origin loads.
2. Who starts the server — you spawn `wmill dev` yourself, OR a
launch.json-aware runtime (currently only the
`mcp__Claude_Preview__*` MCP family) launches it on demand.
The two compose into four common cases (regular browser tab, generic
preview pane, localhost-only preview pane, Claude MCP), each with a
clear instruction. The launch.json/MCP machinery is now scoped to a
single section gated on actually having that tool in your tool list.
Source-only edit in base/preview.md, then regenerated auto-generated/
via python system_prompts/generate.py.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix dev step display
* nit
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* refactor: split flow-dep job tx so subprocesses don't hold row locks
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: link flow version from run page to pinned flow viewer
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: time-out dep job phase 1/3 db ops and surface error on flow page
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: dissolve dep_map in phase 1 and recheck flow version unconditionally
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address PR review — view-latest reload, decimal truncation, app version
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: keep dissolve in phase 3 for relative-import dep jobs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: trim verbose comments and refresh sqlx offline cache
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address cubic — propagate dissolve errors, include workspace in reload key
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(wac): recognize @workflow main, list WAC in scripts/list, run preprocessor
Three workflow-as-code bug fixes:
- #8945: Python WAC template with `@workflow async def main(...)` was not
detected as `auto_kind = "wac"`. The detection only ran when no `main`
function was found. Hoist the heuristic so it runs whether or not `main`
is the entrypoint.
- #8946: `scripts/list?kinds=script` filtered out WAC scripts because they
set `auto_kind = 'wac'` and the SQL hid everything that wasn't NULL.
Allow both NULL and 'wac' (still excluding 'lib' library scripts).
- #8947: Preprocessor functions defined alongside a WAC workflow were
ignored. Inject the preprocessor invocation into the Python WAC wrapper
so it runs before the workflow on the first iteration, then plumb the
preprocessed args through `handle_wac_v2_output` so inline child
re-runs see the post-preprocessor args via `checkpoint.input_args`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(wac): integration tests for #8946 (scripts/list) and #8947 (preprocessor)
- test_scripts_list_includes_wac: hit GET /scripts/list?kinds=script and
assert WAC scripts are in the response (would have failed pre-#8946 fix
because of the auto_kind IS NULL filter).
- test_python_wac_v2_with_preprocessor: deploy a Python WAC script with a
preprocessor, run with raw event args, assert the workflow saw the
preprocessed shape and v2_job.args/preprocessed were updated.
- New wac_preprocessor.sql fixture with auto_kind = 'wac' set explicitly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(wac): address PR review feedback
Five review fixes:
- python_executor.rs: WAC preprocessor now runs inside the wrapper's
`try:` block so failures route through the same `result.json` error
serializer as workflow failures. Switched async-coroutine handling
from deprecated `asyncio.get_event_loop().run_until_complete(...)` to
`asyncio.run(...)` (the recommended primitive on 3.10+).
- bun_executor.rs: when copying preprocessed args into
`checkpoint.input_args`, surface JSON parse failures via `?` instead
of silently coercing to `Value::Null` (which would persist a corrupted
arg into every child re-run). Also collapsed the redundant double
iteration into a single pass.
- windmill-api-scripts/scripts.rs: switched the runnable-script filter
from an allow-list (`auto_kind IS NULL OR = 'wac'`) to a deny-list
(`<> 'lib'`), so future `auto_kind` values aren't silently filtered
from triggers/dropdowns.
- windmill-parser-py: aligned the parser's WAC heuristic with the
runtime detector `is_wac_v2_py` — `@task` is now optional, matching
the runtime which says workflows that only use inline `step()` are
still WAC. Added a regression test `test_parse_python_wac_step_only`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: split git repo viewer effects to remove redundant calls
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: compact file preview header in s3 file picker
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: skip empty preview status row when no message applies
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): preserve case in raw-app runnable filenames
The path assigner lowercased filesystem-safe names, so a raw-app
runnable id like CamelCaseTSRunnable produced a YAML metadata file
keeping the original case but a code file lowercased to
camelcasetsrunnable.ts. On the next push, loadRunnablesFromBackend
paired the two by case-sensitive name match, failed, and registered
the lowercase code file as a separate empty runnable — surfacing as
duplicate runnables in the app editor.
Stop lowercasing in sanitizeForFilesystem and dedupe path assigners
case-insensitively so case-only collisions still get a counter on
case-insensitive filesystems. Also match content/lock files and the
processed-id set case-insensitively in loadRunnablesFromBackend so
repos already pulled by the buggy CLI can recover on the next push
without re-pulling.
Fixes#8939
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): extract readSiblingLock helper, drop test #ref comments
Address review:
- Both call sites in loadRunnablesFromBackend now use the same
case-insensitive lock-file lookup, so a partial-legacy repo with a
mixed-case code file and lowercase lock (or vice versa) works in the
orphan-code branch too, not just the YAML branch.
- Remove `Regression for #8939 …` comments from the new tests; CLAUDE.md
bans referencing the current task/issue from source comments. Test
names already describe the behavior under test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(cli): update assigner-path assertions for case preservation
Three tests in inline_scripts_failure_preprocessor_unit.test.ts asserted
that the assigner produced lowercased filenames (get_users_data, step_b)
from mixed-case summaries — that was the buggy lowercasing behavior.
With case preserved end-to-end, the assigner now returns Get_Users_Data
and Step_B.
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(nativets): forward OTEL-prefixed console logs to tracing events
Nativets jobs run in-process and bypass the handle_child.rs stdout loop
where `OTEL: ` lines are turned into `tracing::event!` calls when
`OTEL_JOB_LOGS=true`. Apply the same prefix handling in the nativets
log receiver so `console.log("OTEL: ...")` reaches the OTEL exporter
like it does for other runtimes.
Moves `OTEL_JOB_LOGS` and `OTEL_PREFIX` into windmill-common so both
crates share the same definition.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(nativets): emit job_log tracing target so logs reach OTEL bridge
For non-native runtimes, `lines_to_stream` → `process_streaming_log_lines`
(EE) emits every stdout line as `tracing::info!(target: "windmill:job_log", ...)`,
which is picked up by the EE `LogContextBridge` and exported to OTEL
(the bridge's filter is `EnvFilter` only, not the targets filter that
drops `windmill:job_log` from stdout/file sinks).
Nativets delivers logs in-process via a channel, so it never goes
through that path and console.log output only reached the Windmill UI.
Emit the same `windmill:job_log` event per line from the nativets log
receiver.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: cli diff & deploy no-op handling + promotion debouncing
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to ed842061576c3ac9b9eb89bb87f6db5b67904474
This commit updates the EE repository reference after PR #551 was merged in windmill-ee-private.
Previous ee-repo-ref: 1210d9f63de8eea4c3a210a10c60fe6382df477b
New ee-repo-ref: ed842061576c3ac9b9eb89bb87f6db5b67904474
Automated by sync-ee-ref workflow.
* test(git-sync): e2e tests for promotion-mode debounce keys
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>
* feat(cli): non-interactive Slack connect/disconnect
Extract create_slack_workspace_artifacts / create_slack_instance_artifacts
from the browser OAuth callbacks and expose them via two new endpoints that
accept a pre-minted xoxb bot token:
- POST /w/{workspace}/workspaces/connect_slack (admin)
- POST /oauth/connect_slack_instance (super-admin)
Both produce bit-for-bit identical DB state to the UI browser flow.
Wire three CLI commands as thin wrappers:
- wmill workspace connect-slack
- wmill workspace disconnect-slack
- wmill instance connect-slack
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): round-trip stability for workspace settings handlers
wmill sync push was destroying UI-configured error_handler/success_handler
state on every deploy. Two orthogonal bugs:
(a) pushWorkspaceSettings called editErrorHandler with `path: undefined`
when the YAML lacked the handler block, which the backend treats as a
clear — so syncing settings.yaml that didn't mention the handler wiped
the DB row. Fix: skip the call entirely when absent from YAML.
(b) edit_error_handler omitted muted_on_cancel / muted_on_user_path when
false, but the CLI always sends them, causing perpetual deepEqual
drift and a spurious editErrorHandler call on every sync push. Fix:
always persist both booleans.
migrateToGroupedFormat now preserves explicit `null` on
error_handler / success_handler as a "clear remote" signal distinct from
absence. Widen ErrorHandlerConfig | null / SuccessHandlerConfig | null to
make this explicit in the type.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): sync support for workspace-level Slack OAuth override
Add slack_oauth_client_id and slack_oauth_client_secret to the v2 tarball
export and to pushWorkspaceSettings, so the workspace-level OAuth override
is now fully managed as code through settings.yaml.
Semantics:
- both defined and truthy → setWorkspaceSlackOauthConfig (upsert)
- both defined but falsy (e.g. empty strings) and remote has a value
→ deleteWorkspaceSlackOauthConfig
- either omitted → leave remote alone ("not managed by git")
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): normalize workspace settings sync to "omit = clear"
Earlier commits on this branch introduced an "omit = keep" rule for
error_handler / success_handler / slack_oauth_client_{id,secret} that
diverged from every other workspace setting (webhook, deploy_to, etc. all
treat YAML as canonical: absence = clear). Normalize:
- v2 tarball always emits these 4 fields (null when remote is NULL) so
round-trip is bijective and settings.yaml is a complete snapshot.
- pushWorkspaceSettings drops the absent-from-YAML guards; YAML is
canonical. Absence and explicit null both clear the remote — same rule
as every other field.
- set_slack_oauth_config / delete_slack_oauth_config now fire
handle_deployment_metadata so UI mutations reach git-sync-enabled
workspaces' committed settings.yaml.
Policy for users: pull before push (same as every other setting). On first
post-upgrade pull, explicit `null` keys appear for any workspace whose
handlers / oauth override are unset — one-time YAML diff, no semantic
change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(cli): add unit + integration coverage for Slack settings sync
Unit tests (settings_unit.test.ts): cover migrateToGroupedFormat preserving
explicit `null` on error_handler / success_handler, and passthrough of
slack_oauth_client_id / _secret (both populated and null values).
Integration tests (slack_settings_sync.test.ts, skipped on CI per the same
convention as datatable_settings_sync.test.ts): exercise the full backend
via withTestBackend to verify
1. pull emits null for unset error_handler / success_handler /
slack_oauth_client_id / _secret;
2. round-trip with all-null handlers is idempotent;
3. push of populated slack_oauth_config upserts;
4. omitting the slack_oauth keys from YAML clears remote (universal
"omit = clear" rule);
5. explicit null error_handler in YAML clears remote;
6. round-trip preserves a populated error_handler exactly, including the
always-persisted muted_on_cancel / muted_on_user_path booleans.
Also feature-gates `use crate::oauth2_oss::workspace_connect_slack` and its
route registration behind `cfg(feature = "oauth2")`: the import caused a
build failure on subsets of the workspace without the oauth2 feature,
surfaced by the integration test harness.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref to 59b6123
Pins windmill-ee-private to the tip of branch alp/slack_cli, which
contains the companion EE changes (helper extraction, non-interactive
Slack connect handlers, git-sync for Slack settings mutations).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update SQLx metadata
* chore: regenerate system prompts for new slack CLI commands
Captures the new workspace connect-slack, workspace disconnect-slack,
and instance connect-slack commands in the auto-generated files that
CI enforces via system_prompts/check-freshness.sh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to b4a5ca11e3b96ff03793c2bd396dbc1fe6ea1022
This commit updates the EE repository reference after PR #550 was merged in windmill-ee-private.
Previous ee-repo-ref: d7e44d0519327ec9077625130365e887826f324b
New ee-repo-ref: b4a5ca11e3b96ff03793c2bd396dbc1fe6ea1022
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>
* [ee] refactor: remove force_branch from git sync settings
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: update ee-repo-ref to 680885a4e8c8de5185650cddeb56b926e722718f
This commit updates the EE repository reference after PR #549 was merged in windmill-ee-private.
Previous ee-repo-ref: 37fe2e1286a162119df885062e50461400631850
New ee-repo-ref: 680885a4e8c8de5185650cddeb56b926e722718f
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: WM_TESTED_RUNNABLE env var + wildcards in test: annotation
Extends the CI test feature so a single test script can cover multiple
runnables and branch on which one triggered it.
- test: annotation now supports glob wildcards: `*` matches one path
segment, `**` matches any depth. A new `ci_test_path_matches` helper
in windmill-common compiles patterns to anchored regexes with a small
quick_cache LRU.
- New migration adds a Postgres GENERATED `has_wildcard` column + partial
index on ci_test_reference so exact-match lookups keep using the
primary index and only wildcard rows are scanned for regex matching.
- ci_test trigger query and the UI `ci_test_results` / `ci_test_results_batch`
endpoints split into exact + wildcard paths; the batch endpoint now
issues one query per distinct kind instead of one per item.
- Worker injects `WM_TESTED_RUNNABLE={kind}/{path}` into CI test jobs,
derived from the trigger metadata stored at push time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: scope CI test job lookup by trigger + populate WM_TESTED_RUNNABLE in resource interpolation
Scope the ci_test_results LATERAL lookup by v2_job.trigger so multi-target
tests (via wildcards or multiple exact annotations) report the correct job
per target. Also pass the tested runnable through transform_json_value in
resources.rs for consistency with schedule_path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 489eb0d89702e5d1cc7c6e0f9ba9e0c8e5063741
This commit updates the EE repository reference after PR #546 was merged in windmill-ee-private.
Previous ee-repo-ref: e7534bcafcd8c27fcf870b2ea868e901b00b7960
New ee-repo-ref: 489eb0d89702e5d1cc7c6e0f9ba9e0c8e5063741
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(autoscaling): return applied_at with UTC timezone in events API
autoscaling_event.applied_at is a naive TIMESTAMP column. Serializing as
NaiveDateTime produces an ISO string with no timezone, which the browser
parses as local time — for users west of UTC this lands in the future and
TimeAgo's Math.max(0, …) clamps every event to "0s ago".
Cast the column with AT TIME ZONE 'UTC' and type the field as DateTime<Utc>
so the response includes a Z suffix.
Also pulls in the EE count-distinct fix for native worker autoscaling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 4128203739a973330599dacfb054203cf9832f3a
This commit updates the EE repository reference after PR #548 was merged in windmill-ee-private.
Previous ee-repo-ref: 32636bc3e3996101554d5ef504785346929a593b
New ee-repo-ref: 4128203739a973330599dacfb054203cf9832f3a
Automated by sync-ee-ref workflow.
* chore: bump ee-repo-ref for applied_at UTC insert fix
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>
* [ee] feat: add auto-login SSO provider instance setting
Adds an instance-level `auto_login_provider` setting that, when set to
an OAuth provider key (e.g. "okta") or "saml", causes the login page
to auto-redirect users to the configured SSO flow on mount.
Useful for orgs with a single SSO where the provider button grid adds
a pointless extra click.
- Backend: new global setting constant, read from DB in list_logins
handler and returned as the `auto_login` field in the response
- Frontend: Login.svelte auto-redirects in loadLogins() when the
configured provider is actually present in the response
- Escape hatch: `?no_sso=1` skips the auto-redirect and shows the
normal login form (admin fallback when SSO is broken)
- No redirect loop: if the `error` prop is set (SSO callback failed),
the redirect is skipped
- Admin UI: new text field under Auth/OAuth/SAML in instance settings
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: skip auto-redirect on /user/login page
Auto-redirect should only fire on embeds where the user did not
explicitly navigate to a login screen (public app popup, approval
pages). Visiting /user/login is an explicit sign-in action — often by
an admin who needs password fallback — so we must never hijack it.
Gate the logic on a new `autoRedirect` prop (default true). The main
login page passes `autoRedirect={false}`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to b7157d55fb9f8d8f7aeb7b1fb69bc935af895a2f
This commit updates the EE repository reference after PR #547 was merged in windmill-ee-private.
Previous ee-repo-ref: e32a48499d206a24e0c12817b465775321b0ee41
New ee-repo-ref: b7157d55fb9f8d8f7aeb7b1fb69bc935af895a2f
Automated by sync-ee-ref workflow.
* fix: handle popup-blocked auto-redirect in popup mode
When Login is embedded with popup=true (public app), auto-redirect
funnels through window.open() without a user gesture — browsers block
it by default, leaving the user stuck on "Signing you in…".
Detect window.open returning null, clean up listeners, reset
autoRedirecting so the provider button grid re-renders, and surface a
toast pointing users at the manual button. The grid click retains its
user gesture and passes the popup blocker.
Also extracts a redirectSaml() helper so the SAML auto-redirect path
and the SSO button click share the same logic.
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>
* feat: add OTEL_HOST_NAME env override for host.name attribute
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to f6bc5647cc41ce348111f781b4a0db2153a28f8a
This commit updates the EE repository reference after PR #545 was merged in windmill-ee-private.
Previous ee-repo-ref: c92441ae0d6d8e89b48677db8cb6b78e5bdae2db
New ee-repo-ref: f6bc5647cc41ce348111f781b4a0db2153a28f8a
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>
* fix: ensure schema is inferred on script/flow module load
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: memoize all WASM parser init promises
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): use wmill.yaml key (not branch name) for workspace-specific filenames
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): resolve --workspace as config key even with --base-url
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(integration): assert workspace config key drives filename suffix in git-sync
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "test(integration): assert workspace config key drives filename suffix in git-sync"
This reverts commit 528993fe29.
* fix(cli): filter reserved keys and preserve validation-skip on non-config --workspace
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add Azure Event Grid triggers (EE)
Introduces a new enterprise trigger kind `azure` that supports three
modes via a single unified trigger type:
- basic_push: Azure Event Grid basic — custom topics, system topics
(Storage, Resource Manager, Key Vault, etc.), domains (push only)
- namespace_push: Event Grid Namespace topics (CloudEvents over HTTP push)
- namespace_pull: Event Grid Namespace topics (HTTP pull with lock-token
ack/reject for dead-lettering)
Auth uses a Service Principal resource (tenant_id, client_id,
client_secret, subscription_id). Subscriptions are created in
CloudEvents 1.0 schema so the push webhook handler and the pull listener
share one payload parser.
Backend
- New crate `windmill-trigger-azure` (OSS stubs + EE impl symlinked from
windmill-ee-private)
- Migration `azure_trigger` table with CHECK constraints enforcing
mode/columns coherence
- `TriggerKind::Azure`, `JobTriggerKind::Azure`,
`DeployedObject::AzureTrigger` variants
- Push route `/api/azure/w/{workspace}/*path` handles classic
Event Grid SubscriptionValidation handshake and CloudEvents 1.0
abuse-protection OPTIONS handshake
- Optional inbound JWT validation (audience check only for v1)
- Feature flag `azure_trigger` propagated through windmill-api,
windmill-store (resource helper), and added to ee_core
Frontend
- `triggers/azure/` editor with mode toggle (basic/namespace-push/
namespace-pull) and per-mode config (topic ARM id / namespace +
topic name / subscription / filters / push auth / pull options)
- Registered in icon map, display names, save functions, badge,
wrapper, editor, add-trigger menu
OpenAPI
- `AzureTrigger`, `AzureTriggerData`, `AzureMode`,
`AzureSubscriptionMode`, `AzureDeliveryConfig`, `TestAzureConnection`
schemas; `/azure_triggers/*` endpoints; client regenerated
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to eaa7c3a9cb37a9ccc93f10a2535d929365acd2d8
This commit updates the EE repository reference after PR #541 was merged in windmill-ee-private.
Previous ee-repo-ref: 9689014e8c12c36c1059fd8fa5758d550b8b8bc9
New ee-repo-ref: eaa7c3a9cb37a9ccc93f10a2535d929365acd2d8
Automated by sync-ee-ref workflow.
* feat(azure-trigger): secret-auth push, ARM discovery, capture isolation, CLI + parity
Frontend:
- Split mode selector into Namespace/Basic + Pull/Push
- ARM resource dropdowns (namespaces, Basic topics, namespace topics)
populated from the service principal; cascade with stale-selection
reset on SP / edition change
- Remove stale authenticate toggle + audience input (server-managed
push_auth_config has replaced them)
- Azure listing page: "Create from template" button; "Also delete Azure
subscription" toggle in the delete modal; simplified trigger label
falling back to path
- AzureCapture.svelte: "Test subscription name" with -wm-capture suffix
- CompareWorkspaces.svelte: wire Azure for fork/compare
- Drop Trigger-deployed/event-loss warning (capture subscription is
isolated with -wm-capture)
Backend:
- Shared-secret push auth (see EE crate for detail)
- JSONB push_auth_config column (renamed from delivery_config), #[serde(skip)]
so clients/CLI/exports never see it
- Drop redundant enabled column; mode supersedes
- Azure capture infra: AzureTriggerConfig + set_azure_trigger_config +
azure_payload route + TriggerKind::Azure arm; PT15M queue TTL on
capture subscriptions so they bound storage after tab close
- Granular ACLs, users offboarding, trash, git-sync deployed-object:
all include azure_trigger
CLI:
- Add azure to TRIGGER_TYPES, pushObj dispatch, getTypeStrFromPath,
trigger commands (get/update/create/list/template), sync delete
switch + regex; e2e test for `trigger new --kind azure`
- system_prompts: SCHEMA_MAPPINGS + schema_names include AzureTrigger;
auto-generated/* regenerated
Skill:
- .claude/skills/adding-a-trigger/ checklist covering every file that
needs editing when wiring a new trigger type (learned from this PR)
ee-repo-ref bumped to b0e490cbf3724b7b64c6a5b010e3bdf24acd873c.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(azure-trigger): ci — ShareModal Kind + regenerated system_prompts
- frontend/src/lib/components/ShareModal.svelte: add 'azure_trigger'
to the Kind type so the listing page's "Permissions" action compiles
(ts2345 — caught by npm_check on CI, missed by fast-check locally).
- system_prompts/auto-generated/: regenerate to drop the stale
delivery_config / AzureDeliveryConfig fields from the Azure schema
(check-freshness on CI).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(azure-trigger): use workspace constant_time_eq crate
Drop hand-rolled constant-time compare in favour of the workspace
constant_time_eq crate (same one used by http_trigger_auth).
ee-repo-ref bumped to 9659382d47286e7f7f66d01b6f5dd8d4ed34848b.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(azure-trigger): pass placeholder + disabled via inputProps
`TextInput`'s `placeholder` and `disabled` go through its `inputProps`
prop — CI's `npm run check` caught the stale top-level passing that
`npm run check:fast` missed. Align with the DefaultEmailConfigSection
pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(azure-trigger): correct LATEST_GIT_SYNC_SCRIPT_PATH version to 28213
The hub deploy of the azure-aware sync-script is version 28213, not
28214. Backend was pinning a non-existent hub script, which broke the
git_sync_e2e suite (every deploy's sync step 404'd).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(azure-trigger): add azure_triggers to token scope selector + skill
- windmill-api/src/token.rs: `build_trigger_scope_domains` was missing
`("azure_triggers", "Azure Event Grid")`, so the CreateToken UI's scope
selector didn't surface azure_triggers:read/write. Backend already had
`ScopeDomain::AzureTriggers` wired (scopes.rs), this just exposes it.
- .claude/skills/adding-a-trigger/SKILL.md: capture both scope-related
files under the hardcoded-arrays section so future triggers don't miss
the UI surface.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(adding-a-trigger-skill): clarify token.rs scope effect
Not a regression — nothing was working before. Skipping TRIGGER_DOMAINS
just means the scope works via API/CLI but has no UI checkbox.
* docs(adding-a-trigger-skill): trim token.rs bullet
* fix(azure-trigger): regen openapi-deref + swap textarea for TextInput
- Run build_openapi.sh to regenerate openapi-deref.{yaml,json} with the
12 azure_triggers paths + schemas. These files are served by the
runtime (include_str! in windmill-api/src/lib.rs) to external SDK
consumers; without this regen the new endpoints wouldn't be advertised.
- Replace the raw <textarea> for event type filters with the
design-system TextInput in textarea mode (frontend/CLAUDE.md bans raw
HTML elements).
Addresses cubic + claude PR review items.
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>
* fix: slim app ai chat context
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: remove stale app chat selection UI
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: trim app chat selected context
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: trim app chat context coverage
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: remove app tool assertion
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: load job metadata on approval page via approval token
The approval page polled getJob without auth, which 400s for non-anonymous
jobs. The page swallowed the error so approvers saw the form but no flow
args, metadata, or graph. Accept the existing approval token on getJob and
skip the non-anon-user check when it validates against the job's flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(approval): address review feedback
- Validate approval token against URL job id directly before resolving
the parent flow, saving a DB roundtrip on the happy path (approval URLs
always carry the flow id).
- Request getJob with no_code/no_logs from the approval page so a
token-bearer only sees what the UI renders (args, raw_flow, metadata).
- Tighten OpenAPI description for the approval_token query param.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add ai agent chat output flag
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: suppress ai agent tool chat messages
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: rename ai agent conversation output flag
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: expose ai agent conversation output toggle
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: gate ai agent chat tab by chat mode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: regenerate system prompts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: address ai agent chat review feedback
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Previously each API replica generated a random Ed25519 signing key at
startup (unless DEBUG_SIGNING_KEY_SEED was set). In multi-replica
deployments this caused "Invalid JWT signature" rejections in the
multiplayer server: the browser could sign a token on pod A while
`windmill-extra` had cached the JWKS public key from pod B.
Derive the seed deterministically from the DB-backed JWT_SECRET using
SHA-256 with a domain-separation tag so all pods agree without
coordination. Re-derive on JWT_SECRET rotation. The
DEBUG_SIGNING_KEY_SEED env var is still honored as an override.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: support windmill chat answer override
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: remove output fallback from chat override
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: handle non-string chat answer overrides
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: persist powershell workspace deps in deployed script lock
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: trigger dep job for powershell scripts on deploy
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: persist flow groups from AI chat tool calls
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: validate group ids and coerce empty groups to undefined
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A BranchOne predicate expression that threw was propagated as a
flow-level error, bypassing the flow's failure_module — especially
silent when nested inside a forloop with skip_failures: true, where
the failed iteration was swallowed with no handler ever invoked.
Catch the predicate-eval error inside compute_next_flow_transform's
BranchOne case, return a new NextFlowTransform::StepFailure variant,
and have push_next_flow_job route it through
update_flow_status_after_job_completion with success=false. Predicate
errors now behave exactly like a failing script step: failure_module
runs when defined, skip_failures still skips, workspace error handler
fires when the flow fails.
Closes#8889
* fix: add flow conversation token scope
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: make flow conversations scope plural
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: update flow chat service import
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: skip opus 4.7 sampling params
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: keep opus 4.7 handling frontend side
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: generalize opus 4.7 model matching
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: stop normalizing opus 4.7 thinking
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: create windmill-ai crate and move base AI types from windmill-common
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move worker AI types to windmill-ai crate
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move QueryBuilder trait and StreamEventSink abstraction to windmill-ai
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add base64 dependency to windmill-ai for bedrock PDF support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add windmill-ai refactor plan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: address PR review — remove dead bedrock feature, add boxed_sink helper, move plan to docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: allow hiding catalog picker and raw input on s3 form fields
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: type itemsType.resourceType instead of casting to any
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: detect and clearly label OOM in zombie flow alerts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address review feedback on zombie flow OOM detection
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: track dollar-quoted strings in SQL block splitter
Queries like `CREATE FUNCTION ... AS $$ ... ; ... $$ LANGUAGE plpgsql;`
were being shredded on every `;` inside the function body because the
SQL splitter's state machine didn't recognize PostgreSQL dollar-quoted
strings. Add an `InDollarQuote(tag)` state so `$$ ... $$` and
`$tag$ ... $tag$` regions are treated as a single quoted span.
Opt-in via a new `track_dollar_quotes` flag on `parse_sql_blocks`;
enabled for PostgreSQL and DuckDB, disabled for MySQL/Oracle/BigQuery/
Snowflake which don't support the syntax.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: make windmill-parser-wasm a self-contained workspace
The wasm parser crate is excluded from the backend workspace (its
nightly-only `cargo-features = ["panic-immediate-abort"]` would break
stable cargo on the whole workspace), but its manifest still used
`.workspace = true` inheritance — which fails with "failed to find a
workspace root" once the parent no longer considers it a member.
Declare the crate as its own workspace by adding `[workspace]`,
`[workspace.package]`, and `[workspace.dependencies]` tables. Mirror
the relevant entries from the parent `backend/Cargo.toml` (same
version specs, same path targets) so resolution stays byte-identical
to what the parent would have produced.
Also:
- Teach `.github/change-versions.sh` (+ mac variant) to update this
crate's own `Cargo.toml` version and bulk-bump the `windmill-*`
entries in its `Cargo.lock` on each release.
- Bump the frontend's pinned `windmill-parser-wasm-regex` to 1.688.0
to match the freshly-built package, and refresh `package-lock.json`.
- Regenerate the wasm crate's `Cargo.lock` from scratch (first build
under the new workspace re-resolves the full graph; target-gated
deps from sibling crates like `windmill-parser-py-imports` are
now recorded in the lockfile but not compiled when targeting
wasm32).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract the MSSQL s3 ingest+upload logging pattern into a reusable
`s3_stream_and_upload_with_logs` helper and apply it to the PostgreSQL,
MySQL, OracleDB, BigQuery, and Snowflake executors. Each s3 streamed
query now emits periodic progress lines, an ingest-done line, and an
upload+transcode-done line to the job output, matching MSSQL.
`convert_json_line_stream` now returns `BoxStream<'static, _>` so the
output stream can be forwarded to `s3.upload` from inside the generic
helper without lifetime gymnastics; the two existing callers already
boxed the result, so behavior is unchanged.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add cli eval behavior checks
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: harden cli eval command parsing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add proxy transport for ai evals
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: strip propertyNames for gemini schemas
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: require explicit eval transport
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: async dep endpoints and queue-position logs in cli
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: share logQueueStatus between dev.ts and job_polling.ts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: continue polling on transient errors in job_polling.ts
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: batch cancel silently dropping jobs from other workspaces
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: gate cross-workspace batch cancel on all_workspaces flag
Enforce path workspace unless all_workspaces=true, so cross-workspace
selections only succeed when the UI is actually in all-workspaces mode.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* sqlx
* fix: gate cancel_selection all_workspaces on admins workspace
Matches the convention used by count_queue_jobs and count_completed_jobs_detail
in the same file — cross-workspace scope is only honored when the path
workspace is "admins", preventing clients in regular workspaces from dropping
workspace scoping by passing all_workspaces=true.
Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>
* perf: speed up mssql s3 ingest and add phase logs to job output
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: drop mssql s3 progress interval to 10s for better visibility
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: drop transient tests that compared against removed code path
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: log boolean predicate eval errors to root flow logs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: use if-let over match for predicate error logging
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Clearing the parallelism input field now sets parallelism to undefined
instead of creating an object with an empty value, preventing the
'parallelism value is null' error on execution.
Closes#7864
Assisted-by: GLM 5.1
Co-authored-by: Abhay <abhayjnayakpro@gmail.com>
* refactor: make flow chat code edits explicit
* refactor: centralize flow tree lookups
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: simplify flow chat tree mutations
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: reuse flow tree lookup in schema map
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: remove flow lookup alias
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: reuse flow tree in previous results
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: reuse canonical flow module lookup
* fix: align rebased flow helpers
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: remove flow chat cleanup plan
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: remove flow chat helper wrappers
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: preserve non-flowmodule AI agent tools in skeleton and previous_result
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: consolidate flow module ID collectors into flowTree
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: search full flow tree in test_run_step to find special modules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: recurse into aiagent tools in collectAllFlowModuleIds
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* docs: add job-debugging guidance to wmill init output
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update cli-commands skill description in generate.py source
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: populate wmill.d.ts schemas in wmill app dev
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: seed inferred schemas at wmill app dev startup
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add homepage connect drawer
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: reset connect drawer state
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: polish home connect button
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use standard home connect button style
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* chore: record app benchmark baseline
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: strengthen app benchmark persistence checks
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: seed inventory tracker benchmark case
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add deterministic app diagnostics
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add app chat patch_file tool
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: add app session id micro-edit case
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: narrow app patch file content
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: stop gating app evals on lint
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Adds an instance-level toggle that hides the email/password form on the
login page and rejects password login, password reset request, and
password reset endpoints server-side. Useful for OAuth/SAML-only
deployments.
- New `disable_password_login` global setting + lazy_static AtomicBool
- `load_disable_password_login` loader wired into monitor initial_load
and notify_global_setting_change listener
- Unauthenticated `GET /auth/is_password_login_disabled` endpoint so the
login page can hide the password form when enabled
- Toggle in Instance Settings → Auth/OAuth/SAML
- Login.svelte hides the password form and the "Log in without
third-party" toggle when the setting is on
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add GitHub as a native trigger service
Add GitHub webhooks as a native trigger, allowing users to trigger
scripts/flows from repository events (push, PR, issues, etc.) via
OAuth-based webhook management.
Backend:
- DB migration adding 'github' to native_trigger_service, TRIGGER_KIND,
and job_trigger_kind enums
- Full External trait implementation: create/update/delete/get webhooks,
per-trigger sync verification, webhook payload preparation
- Paginated repos endpoint (up to 1000 repos)
- OAuth flow with admin:repo_hook and read:user scopes
Frontend:
- GitHub trigger form with repo picker and MultiSelect event selector
- Workspace integration settings with setup instructions
- Trigger badge, editor, and wrapper integration
- GithubIcon updated to support size/class props (matching other icons)
- Hub template reference for starter scripts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show GitHub in sidebar when triggers exist
Add github_used to the getUsedTriggers endpoint so the sidebar picks up
GitHub as an active trigger kind. Also document this step in the native-
trigger skill so future services don't miss it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: on-demand GitHub repo search instead of bulk fetch
Replace the upfront pagination through all repos with a debounced search
flow: load 30 most-recently-updated repos by default, then query GitHub's
/search/repositories API (scoped to the authenticated user via user:@me
and restricted to name matches via in:name) as the user types.
Frontend uses runed's Debounced + resource to wire the Select's filterText
to the backend query with 300ms debouncing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: request `repo` OAuth scope to list private GitHub repos
`admin:repo_hook` grants webhook management but not repo listing — so
/user/repos and /search/repositories returned only public repos. Switch
to `repo` (full repo scope, which is a superset and also covers webhook
management).
Users who already connected GitHub need to disconnect and reconnect to
pick up the broader scope.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* revert: fetch all GitHub repos upfront instead of searching on demand
Revert the debounced search flow — paginate through /user/repos (up to
1000) on form open. Simpler UX: repos are all there from the start, the
Select's built-in client-side filter handles finding one.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: typed 404 detection + add GitHub flow template reference
Replace fragile e.to_string().contains("404") matching with a proper
http_error_status helper that downcasts through anyhow to the typed
HttpRequestError and reads the StatusCode.
Also wire the hub flow template (id 80) into NATIVE_TRIGGER_SERVICES.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update GitHub script template hub ID to 28202
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: align GitHub trigger with Nextcloud/Google patterns
Addresses review feedback from Claude and cubic.
Backend:
- `delete()` now only swallows NotFound (DB missing row) and 404 (API
webhook already deleted); non-404/DB errors propagate so callers know
cleanup failed. Matches Nextcloud's delete pattern exactly.
- `get_owner_repo_from_db` returns `Result<Option<(String, String)>>`
instead of an error on missing row (matches Google's delete flow).
Frontend:
- `loading: boolean` (required) + `$bindable()` with no default — matches
Nextcloud, satisfies CLAUDE.md banned-pattern rule.
- Wrap `loadRepos()` in `$effect` reacting to `$workspaceStore` so repos
load once the store is available and refresh on workspace switch.
- Replace raw `fetch('/api/.../native_triggers/github/repos')` with the
generated `NativeTriggerService.listGithubRepos(...)` typed client.
Adds `/repos` route + `GithubRepoEntry` schema to openapi.yaml.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: include app owner in GitHub App installation URL for GHE Cloud
GHE Cloud custom domains (*.ghe.com) require the owner (org/user) in
the app installation URL path: /apps/{owner}/{slug}/installations/new.
Adds an optional app_owner field to the GHES app config.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 2c2b8dc99689f54b8cd916fb9472fd5698b09478
This commit updates the EE repository reference after PR #539 was merged in windmill-ee-private.
Previous ee-repo-ref: 40dd503d8c563ff93fd2ee3fd8830a5b1c4428d2
New ee-repo-ref: 2c2b8dc99689f54b8cd916fb9472fd5698b09478
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: serve populated jwks at /.well-known/jwks.json for vault
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: gate jwks route on private feature and use oidc_oss
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: [ee] migrate slack OAuth to v2
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: regenerate openapi-deref and make SlackToken.team optional
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to a28f3509d0aa7c0e17fa6dcb1d03d935a7a2a11c
This commit updates the EE repository reference after PR #540 was merged in windmill-ee-private.
Previous ee-repo-ref: d149fa6fcb90c4833bbdbd876c0466b5a6196c1c
New ee-repo-ref: a28f3509d0aa7c0e17fa6dcb1d03d935a7a2a11c
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: update on_behalf_of_email in app policy on offboarding
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* sqlx
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: workspace specfic tags compatibility with forked workspaces
* Rename _db to db and use saved WM_FORK_PREFIX
* Add ttl cache for mapping fork id to parent workspace id
* Change second option to just have a -fork suffix
* fix: use compact json for flow patches
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: improve flow eval harness
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: record flow benchmark history
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: preserve schema in set flow json
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* style: clean set flow json schema guard
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: clean flow patch review followups
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: avoid persisting system prompts in ai memory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: keep ai memory cleanup write-side only
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add bedrock prompt caching for claude
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: add bedrock memory regression
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: gate bedrock prompt caching by model id
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: link bedrock caching allowlist source
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: include workspacedependencies in default git sync include_type
Workspace dependencies were not synced by default because the default
include_type arrays did not contain 'workspacedependencies'. This meant
users had to manually add the type to their git sync config.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add missing folder to new-format fallback and align all defaults
- Add 'folder' to the new-format repo fallback default include_type
(was missing unlike the other 4 locations)
- Add 'workspacedependencies' to legacy defaultTypes fallback
- Align GitSyncFilterSettings prop default with context defaults
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] fix: per-branch concurrency key for promotion-mode git sync jobs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref for per-branch concurrency key
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 7bc0fdb8647268c7afa67b4b0bed69c897eaf92a
This commit updates the EE repository reference after PR #537 was merged in windmill-ee-private.
Previous ee-repo-ref: b933874649a63c5266a33360a95e3c163acc6b5f
New ee-repo-ref: 7bc0fdb8647268c7afa67b4b0bed69c897eaf92a
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
The root job fetch at the start of get_flow_job_debug_info was missing
.with_auth(&opt_authed), which left the anonymous-user access check
disabled for this endpoint. Child jobs in the same handler already had
the check. Unauthenticated callers holding a flow job UUID could fetch
job arguments of non-anonymous jobs, including secrets passed as flow
inputs.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: support special flow modules in evals
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: extract shared flow helper logic
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: make special flow tools openai-compatible
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: improve flow eval prompts and validation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: relax flow benchmark overfits
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: record updated flow benchmark history
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: address flow review findings
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: source flow chat special module prompt
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: narrow rawscript helper return type
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: dedupe flow chat prompt guidance
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: relax flow test10 validation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add backend preview validation to ai evals
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: refresh shared preview workspace assets
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: harden shared backend preview validation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: cascade trigger script_path updates on script/flow rename + fix trigger permissioned_as
Backend: When a script or flow path is renamed, automatically update script_path
across all trigger tables (http, email, kafka, websocket, postgres, mqtt, nats,
sqs, gcp, native). Long-running triggers get server_id reset to force restart.
Native triggers additionally get async webhook URL re-registration with external
services (Google, Nextcloud) via token rotation + handler.update().
Frontend: Fix permissioned_as handling across all trigger/schedule editors:
- Allow setting permissioned_as on trigger creation (not just edit) for admins
- Fix hasChanged detection for permissioned_as changes
- Fix FolderEditor group selector showing usernames instead of group names
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: rename script_rename -> runnable_rename for consistency
"Runnable" is the correct term for both scripts and flows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove native trigger re-registration from runnable rename
Keep it simple — only update script_path in the DB for non-native triggers.
Native triggers require external service re-registration (token rotation +
webhook URL update) which adds significant complexity; defer to a future PR.
sqlx files for the updated CTE query need regenerating.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* sqlx
* refactor: call update_triggers_script_path directly, remove windmill-trigger wrapper
No need for the extra module/dep — the common function is called directly
from scripts.rs and flows.rs with inline error mapping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reject empty principal in folder default permissioned_as validation
`u/` and `g/` (no name after prefix) were passing validation. Use regex
to require at least one character after the prefix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent async folder-default load from overwriting user's permissioned_as choice
Split the initialization effect into two: one that resets on trigger switch
(tracks permissionedAs), and one that handles folder default loading (tracks
folderDefault.value). The second effect is guarded by a userHasSelected flag
set in handleSelect, so a late-arriving folder default doesn't wipe the
user's explicit selection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* lock
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Flow substeps that inherit the parent flow's tag were re-validated
against CUSTOM_TAGS, which rejected dedicated flow tags
(`{workspace_id}:flow/{path}`) since they are never user-registered.
The parent flow's tag was already validated at push time, so skip the
redundant check when the substep simply inherits it.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: improve permissions drawer UX and auto-share resource variables
- When sharing a resource, automatically detect linked variables ($var: refs)
and offer to apply the same permission changes via a toggle (on by default)
- Rename "Share" to "Permissions" across all dropdown menus (resources, variables,
scripts, flows, apps, schedules, triggers)
- Replace Share icon with Shield icon for consistency
- Show default permissions (folder/user/group) as a separate section in the drawer
- Move item path into drawer title ("Permissions for {path}")
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: guard async results against stale drawer state and null-safe extra_perms
- Add path staleness check in loadLinkedVarPaths and loadDefaultPerms
to prevent late async responses from overwriting state when the drawer
was reopened for a different item
- Use ?? {} fallback for folder.extra_perms which can be undefined
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The total duration of a for-loop/branchall group was computed as the
naive sum of all iteration durations. This is wrong for parallel
execution and doesn't account for orchestration overhead. Instead,
compute actual wall-clock time as max(completed_at) - min(started_at).
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract load helpers from reload_setting family
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: convert atomic primitive globals to AtomicBool/AtomicI64
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: convert CRITICAL_*/HUB_API_SECRET/INSTANCE_EVENTS_WEBHOOK/JWT_SECRET to ArcSwap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: pin ee-repo-ref to arcswap-refactor EE branch commit
* refactor: convert BASE_URL/HUB_BASE_URL/MIN_VERSION/LICENSE_KEY*/LICENSE_KEY_ID to ArcSwap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: convert worker hot-path globals to ArcSwap (WORKER_CONFIG et al)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: pin ee-repo-ref to combined arcswap-urls+worker EE commit
* chore: update ee-repo-ref to d8be8f88cb8898c8f6b27421989d53528223815d
This commit updates the EE repository reference after PR #532 was merged in windmill-ee-private.
Previous ee-repo-ref: c375aaaac9ec0fc0480993627d0defc8054c31a4
New ee-repo-ref: d8be8f88cb8898c8f6b27421989d53528223815d
Automated by sync-ee-ref workflow.
* fix: cleanup unused imports + fix 2 missed WORKER_CONFIG readers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to ce0f8fbbbde09c4a858312d2d8716d224e99042c
This commit updates the EE repository reference after PR #534 was merged in windmill-ee-private.
Previous ee-repo-ref: 450b601b5aba0ca0b2045f4b5071aa8701b4bfb7
New ee-repo-ref: ce0f8fbbbde09c4a858312d2d8716d224e99042c
Automated by sync-ee-ref workflow.
* fix: secret_backend_integration test — BASE_URL.write().await → .store()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: convert APP_WORKSPACED_ROUTE to AtomicBool for symmetry with HTTP_ROUTE_WORKSPACED_ROUTE
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to e587df8 (post-#535 merge)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: persist indexer max_index_time_window_secs setting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: toggle UX for indexer time window cap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
is_wac_v2_py required both @workflow and @task, so a workflow using
only inline step() calls fell through to the regular Python path and
returned the raw coroutine object instead of its awaited result. Match
the TS detector and accept @workflow alone.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The response schema used `oneOf: [$ref, {type: null}]` which is
OpenAPI 3.1 syntax, but the spec is declared as 3.0.3. Both
oapi-codegen (Go) and openapi-python-client rejected it, breaking
the client release jobs. Switched to the standard 3.0 pattern
(`nullable: true` + `allOf: [$ref]`), matching existing usage at
openapi.yaml:21410.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: enrich OTEL log records with per-request LogContext
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add otlp_smoke example for manual OTEL log bridge verification
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 5d6b713b74fc46735807f5c32883002e8d976fbc
This commit updates the EE repository reference after PR #529 was merged in windmill-ee-private.
Previous ee-repo-ref: 45959d063bc941c567488d330b5819601cdd2d3d
New ee-repo-ref: 5d6b713b74fc46735807f5c32883002e8d976fbc
Automated by sync-ee-ref workflow.
* refactor: store LogContext in ArcSwap instead of Mutex
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: pin ee-repo-ref to ArcSwap branch commit
* chore: update ee-repo-ref to be2f3d4d11bb7110200524d7157caab3aac53996
This commit updates the EE repository reference after PR #530 was merged in windmill-ee-private.
Previous ee-repo-ref: 45b4d7963a9ebcd583d1a87abe7d07d3d521584a
New ee-repo-ref: be2f3d4d11bb7110200524d7157caab3aac53996
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* refactor: extract flow delete helpers
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: unify flow delete planning
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: stabilize flow delete execution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: simplify flow delete plan execution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: allow non-admin users to create HTTP triggers with forced workspaced routes
Non-admin users can now create and fully edit HTTP triggers, but are forced
to use workspaced routes (workspace-prefixed URLs). Instance-wide routes
remain admin-only to prevent cross-workspace URL conflicts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add missing RLS INSERT/DELETE policies for http_trigger table
Non-admin users were blocked by row-level security when creating HTTP triggers.
Added INSERT, DELETE, see_own, and see_member policies matching other trigger tables.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: allow user paths for HTTP triggers
Remove the hideUser restriction on the Path component so HTTP triggers
can be created under user paths (u/username/...) in addition to folder paths.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove added note from instance settings description
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: allow non-admins to edit non-workspaced routes without changing route config
Non-admins can now open and edit existing non-workspaced HTTP triggers
(created by admins) as long as they don't modify route_path, http_method,
or workspaced_route. The workspaced prefix is only forced on new triggers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: allow non-admins to change route_path on workspaced routes
The prevent_route_path_change DB trigger blocked all route_path changes
for windmill_user, even on workspaced routes. Now only instance-wide
(non-workspaced) routes are protected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add explicit GRANT and force workspaced routes in OpenAPI generator
- Add explicit GRANT INSERT, DELETE on http_trigger to windmill_user
for safety on customer instances
- Force workspaced_route: true for non-admins in OpenAPI route generator
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add deploy restriction rule and fork review requests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt for fork review requests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review comments on fork review requests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: rename fork review requests to deployment requests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt for deployment request rename
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: inline deployment request panel into deploy layout
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: place Request deployment button to the left of Deploy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: inline fork triggers into main deploy list
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: open real trigger detail drawer for inline fork triggers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: email notifications for merge completion and reply pings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update deployment_request + protection_rule tables on workspace id rename
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 972893c3870e4c4a70a35748abed282d88904805
This commit updates the EE repository reference after PR #528 was merged in windmill-ee-private.
Previous ee-repo-ref: 5684d1c17d930b17849c1e5d7577891e64682d45
New ee-repo-ref: 972893c3870e4c4a70a35748abed282d88904805
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add instance-level ruff config auto-pulled by LSP container
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move ruff config to new LSP tab in instance settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: silence user-facing toast for non-critical hub script tracking error
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* n
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: display message and web search content in agent graph node status
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: use markdown renderer for agent message output
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: revert web search output display — content not useful
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve web search alert text
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: align message title styling with other node status sections
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add folder default_permissioned_as rules for ownership defaults on deploy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unnecessary auth guard on default_permissioned_as — rules are advisory only
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts with new CLI commands
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CI review findings — TOCTOU, race condition, email validation, type coercion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add sqlx offline cache for test queries (fixes cargo_test CI)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address remaining review findings — incomplete request bodies, dead code, redundant import
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address remaining review findings — full script fields, reactive stores, catch-all validation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: app/schedule/trigger set-permissioned-as fetch remote first to avoid data loss
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: app set-permissioned-as avoid creating redundant app version
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: compact user/group toggle + select for folder default_permissioned_as rules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: collapse default_permissioned_as section by default in folder editor
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: include default_permissioned_as in FolderFile CLI type for YAML round-trip
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: process folder.meta changes before items in push to apply new rules immediately
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: clone default_permissioned_as on fork/rename + add full lifecycle tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add no-op guarantee test — folder without rules behaves like before
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: rename cliBehavior to syncBehavior — more accurate scope
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: enrich hanging flow error with worker and service log info
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review on hanging flow diagnostics
- Widen log_file lookup window to [-90s, +30s] around worker last ping
so the batch containing the crash is captured (log files are
minute-aligned; looking forward only was missing the relevant bucket).
- Log a warning on log_file query errors instead of silently swallowing,
so a misconfigured table is not reported as "no log files found".
- Note that service log download URLs require S3/parquet collection.
- Fix memory display when only worker_memory_total is known.
- Regenerate sqlx offline cache for the new/modified queries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Git sync runs as a DeploymentCallback job. When the OTEL MITM tracing
proxy is enabled, all HTTP/HTTPS traffic from the script is rerouted
through a local intercepting proxy that chains to the corporate upstream
proxy. Git's HTTPS to GitHub fails in this setup (TLS interception with
chained CONNECT tunneling is fragile, and git's CA env handling diverges
from what the proxy injects), so customers see "GitHub.com URL couldn't
be reached" until they disable OTEL.
Detect DeploymentCallback jobs in get_proxy_envs_for_lang and fall back
to the stock PROXY_ENVS so git talks to the corporate proxy directly,
unmodified. The git sync script is system code; we don't need HTTP spans
for it.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CiTestResult was defined between the INLINE START/END markers, which
python-client/build.sh strips and replaces with a wildcard $ref to
openflow.openapi.yaml, breaking the PyPI publish job.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] feat: add instance onboarding telemetry
Update ee-repo-ref to include instance_onboarding telemetry field
in the daily stats payload.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 5f912375340225876a8c1740c3301f39cd6cbd6d
This commit updates the EE repository reference after PR #527 was merged in windmill-ee-private.
Previous ee-repo-ref: b0b10d81060ab6dabee81a5a067ffadc6b48e074
New ee-repo-ref: 5f912375340225876a8c1740c3301f39cd6cbd6d
Automated by sync-ee-ref workflow.
* sqlx
---------
Co-authored-by: Claude Opus 4.6 (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: thread concurrency semaphore through dedicated worker executors
Pass the concurrency_semaphore parameter through bun, deno, and python
start_worker functions to handle_dedicated_process. Also fix the
DedicatedWorkersSelector to use listWorkspacesAsSuperAdmin (so all
workspaces including admins are visible) and skip loading when disabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to limit-workers-one-job branch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 54037e77cdd37777560755fef7075d35906c96d8
This commit updates the EE repository reference after PR #523 was merged in windmill-ee-private.
Previous ee-repo-ref: 56890ea8fca2c1c44a1338a27011b4dd1137d9c9
New ee-repo-ref: 54037e77cdd37777560755fef7075d35906c96d8
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (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: treat empty global setting strings as unset
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: close protected-setting whitespace gap in diff and preserve empty ws override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The new workspaces: section in wmill.yaml lets the CLI auto-select the
right entry by matching baseUrl + workspaceId against the existing
--base-url and --workspace flags the backend already passes, making the
force_branch override redundant.
Backend field and serializer are intentionally left intact for backward
compat with any repository that already has force_branch saved.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] feat(vault): add skip_ssl_verify option for HashiCorp Vault
Adds an optional skip_ssl_verify boolean to VaultSettings so
self-signed Vault deployments can be used in development without
needing a custom CA bundle. The flag is surfaced as a Toggle in the
HashiCorp Vault section of the secret backend instance settings and
plumbed through to the EE Vault HTTP client builder.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to bcfb663f9e902539abbbf69c517715eb8d4ce8f9
This commit updates the EE repository reference after PR #526 was merged in windmill-ee-private.
Previous ee-repo-ref: 7e1372b8f59fe81aaf61212970ebdf2286be864d
New ee-repo-ref: bcfb663f9e902539abbbf69c517715eb8d4ce8f9
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* [ee] feat: add external JWT tokens listing in instance settings
Add the ability for superadmins to view all external JWT tokens that have
been used for authentication, along with their claim metadata.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move external JWT tokens listing to users tab
- Move list endpoint from /oidc/ext_jwt_tokens to /users/ext_jwt_tokens
- Display as a sub-tab below the instance Users tab, only shown when tokens exist
- Use DataTable's built-in load-more pattern for pagination
- Add "Recently active only" toggle (tokens used in the last 30 days)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add dev_override cargo feature to windmill-common
* feat: show placeholder for legacy external JWT entries
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 62a462461271b900351c18b0ab1ca78651154b2a
This commit updates the EE repository reference after PR #524 was merged in windmill-ee-private.
Previous ee-repo-ref: 7b493a337abe00a47cf9d94847babe3cb3a6799f
New ee-repo-ref: 62a462461271b900351c18b0ab1ca78651154b2a
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: CLI falls back to workspace whoami when global whoami is 401
Workspace-scoped tokens (token.workspace_id set) cannot call
/api/users/whoami — the backend's token lookup filters by workspace_id
which is NULL on global paths, so auth returns 401 before the handler
runs. This breaks the CLI entirely: requireLogin calls globalWhoami at
the start of every command, so no command works with a
workspace-scoped token, not even `wmill workspace whoami`.
Fix it CLI-side: if the global whoami returns 401, fall back to the
workspace-scoped /api/w/{w}/users/whoami using the workspace already
known from the CLI profile, and adapt the response shape to
GlobalUserInfo. Also drop the redundant second globalWhoami call in
`wmill workspace whoami` — use requireLogin's return value instead.
No backend changes: the workspace_id binding on the token stays
strictly enforced for every global endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use name-based ApiError check in whoami fallback
Review feedback from PR #8789: `instanceof ApiError` can silently
return false when bundling produces multiple module instances of
`gen/core/ApiError.ts` (bun build for npm, JSR dev path), which would
skip the workspace-whoami fallback and reintroduce the exact bug this
PR fixes. Match the name-based check already used at
`cli/src/main.ts:232` and drop the `ApiError` import.
Also add a comment on `workspaceUserToGlobalUserInfo` listing the
fields that aren't derivable from the workspace-scoped User response
and are filled with placeholder values, so future callers don't trust
them downstream.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: unify CLI config to workspaces, deprecate gitBranches/environments
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: update frontend examples and regenerate system prompts for workspaces config
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: update test files to use workspaces config instead of gitBranches
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: handle --branch with --base-url correctly in sync pull/push
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: warn when --workspace overrides auto-detected branch or misses config entry
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: show reason why workspace was selected in log message
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: clarify specificItems file naming uses gitBranch as suffix
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: rename branch-specific to workspace-specific, use workspace name as file suffix
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: rename branch-specific to workspace-specific, add comprehensive integration tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: simplify bind and init to be workspace-centric
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: make bind/unbind interactive with --workspace and --branch flags
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: make bind interactive with profile selection, workspace name, and optional branch
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: init offers to bind workspace using same flow as wmill workspace bind
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: skip backend git-sync check in init when no workspace was bound
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: skip all API calls in init when no workspace was bound
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: log when RT namespace is skipped, offer to generate it after bind
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: warn when no workspace bound during init
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: init git-sync check uses bound workspace, not active profile
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: init uses selected profile directly, avoids re-resolving and duplicate prompt
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: init skips requireLogin, uses bound profile token directly
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: auto-pick or prompt workspace from config when no branch matches
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: show configured workspaces list and bind hint in resolution messages
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: cache bound profile to avoid duplicate profile selection prompts in init
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: hoist boundProfile scope, add 2 comprehensive integration tests covering all flows
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: rt.d.ts prompt defaults to no when file exists, better description
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: remove empty overrides from generated config, add specificItems hint
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: add inline comments for non-trivial fields, add overrides/promotionOverrides hints to bound workspaces
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: regenerate system prompts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add user offboarding flow with object reassignment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: require new_operator for permissioned_as when reassigning to folder
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update on_behalf_of_email on scripts/flows during offboarding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract offboarding to separate module and add integration tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: delete tokens, add operator preview counts, remove token reassignment UI
Tokens are now always deleted during offboarding. Preview now shows
scripts/flows/apps with on_behalf_of and schedules/triggers with
permissioned_as referencing the departing user (even outside their path).
Token reassignment UI removed since webhooks break on path changes anyway.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: rich preview with path lists, warnings, and downloadable report
Preview now returns full path lists (not just counts) for owned objects
and objects executing on behalf of the user. Adds warnings for:
- HTTP triggers (webhook URLs will change)
- Email triggers (addresses will change)
- Broken $var:/$res: references in resources/variables
Frontend provides "Export list" button to download affected content.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add coverage for dynamic queries (triggers, extra_perms, operator schedules)
Adds HTTP trigger, extra_perms reference, and shared schedule to test
fixture. Tests verify that non-macro sqlx queries (trigger reassignment,
extra_perms cleanup, operator schedule update) work correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove broken_references, add full dynamic query test coverage
Remove broken_references field from preview (user's resources/variables
are already in the owned paths list). Add shared HTTP trigger fixture
to test all dynamic query paths: trigger operator preview (line 232),
trigger permissioned_as update for non-user-path (line 951), and
extra_perms cleanup on trigger tables (line 983).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add referencing field to preview for content/value path references
Preview now includes a 'referencing' section listing scripts (by content),
flows (by value JSON), apps (by policy/extra_perms), and resources (by value)
that contain references to u/{username}/ paths. These references may break
after reassignment. Shown in export list and as a warning in the UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: rename objects to items in UI, detect on_behalf_of items in hasItems
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace remaining objects with items in UI text
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: rename operator to on_behalf_of, separate owned vs on-behalf UI sections
- Rename new_operator to new_on_behalf_of_user in API and frontend
- Rename op_ prefixed variables to obo_ in backend
- UI now shows separate sections for owned items and items running
on behalf, with the operator selector shown only when needed
- canSubmit logic updated: operator needed for folder targets OR
when on-behalf items exist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: CSV export, side-by-side layout, always accept new_on_behalf_of_user
- Export affected items as CSV instead of text
- Owned items and on-behalf items shown side by side in summary boxes
- new_on_behalf_of_user always accepted (defaults to target user for
user targets, required for folder targets)
- On_behalf_of selector always visible, auto-defaults when user target
is selected
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: proper pluralization and bottom-aligned counts in summary boxes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: stack info boxes vertically, referencing box as warning style at top
Info boxes (owned, on-behalf, referencing) now one per row instead of
side-by-side. Referencing box uses warning colors. Webhook/email trigger
alerts shown below boxes. Proper pluralization in global modal too.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: CSV exports only referencing items, export button inside warning box
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: use ToggleButtonGroup for user/folder, add reassign toggle on remove
- User/Folder selection now uses ToggleButtonGroup component
- When removing a user, a "Reassign items before removing" toggle lets
the admin skip reassignment and just delete directly
- In reassign-only mode, the toggle is not shown (always reassigns)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show token details with labels and scopes in preview
Preview now returns token label, scopes, and expiration instead of just
a count. Frontend shows a dedicated token box listing each token with
its scopes. Test updated to verify token label in preview response.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: extract shared offboarding components, per-type trigger links, hash deep linking
- Extract OffboardItemsBox, OffboardReassignControls, OffboardWorkspaceSection,
and offboarding-utils.ts as shared components used by both workspace and global modals
- Change triggers in OffboardAffectedPaths from Vec<String> to HashMap<String, Vec<String>>
so frontend knows which trigger page to link to
- Add hash-based deep linking to all 9 trigger pages and schedules page
- Preserve URL hash in updateQueryFilters across all trigger pages
- Only open editor drawer if the item is found in the list
- Reassign toggle at top with warning alert when disabled (both modals)
- Referencing items box uses yellow warning variant with expandable path links
- Cleaner labels: "Move u/{username}/* items to", "Update triggers/runnables permissions to"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: rename on_behalf_of section label to match flow advanced settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate sqlx query cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review issues for offboarding
- Add 9 trigger tables to check_path_conflicts for user-friendly conflict messages
- Fix submit button no-op when user has only on-behalf items (show target selector, fix canSubmit)
- Only delete workspace user when reassignment entry exists (prevent orphaned objects)
- Add $azure_kv: prefix to vault secret query (match rename_user pattern)
- Use Svelte 5 onSelected callback instead of deprecated on:selected
- Make ScriptBuilder section label conditional on canPreserve
- Fix CSV export to include trigger paths via flattenPaths utility
- Fix test_offboard_reassign_only to remove conflicts and assert on response
- Parallelize workspace config fetches in global modal with Promise.all
- Delete tokens when deleting workspace user
- Return structured JSON from global offboard endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* sqlx
* fix: address second round of PR review issues
- Accumulate per-workspace OffboardSummary in global offboard instead of returning zeros
- Delete workspace user unconditionally when delete_user=true (prevent orphaned usr rows)
- Filter archived/deleted scripts in check_path_conflicts to match preview
- Reset form state when workspace offboard modal reopens
- Move hashHandled=true inside trigger-found guard on all 10 deep-link pages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: improve offboarding integration tests
- Add second workspace to fixture for multi-workspace global offboard testing
- Add test_global_offboard_execution: verifies items reassigned across 2
workspaces, user deleted from both, and password row deleted from instance
- Add test_offboard_invalid_target: verifies 400 for nonexistent user,
nonexistent folder, and invalid target format
- Fix test_offboard_to_user: use single DELETE, add explicit new_on_behalf_of_user
- Fix test_global_offboard_preview: assert 2 workspaces instead of 1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address third round of PR review issues
- Fix ScriptBuilder tooltip to match conditional section label wording
- Clear stale conflicts in global modal on reopen
- Fix test_offboard_to_folder to assert on specific moved path, not pre-existing data
- Allow deleting user with zero items (show Offboard button, skip reassignment)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add global token deletion warning in instance-level offboard modal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* update sqlx
* fix: add raw_app path and dependency_map path reassignment to offboarding
Audit found these tables with user-scoped paths were not being updated:
- raw_app: mirrors app paths, needs path reassignment
- dependency_map: importer_path and imported_path reference user paths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move user cleanup to delete_workspace_user_internal, fix review issues
- Move extra_perms, folder owners, drafts, favorites, inputs, captures
cleanup into delete_workspace_user_internal so any user deletion gets
proper cleanup (not just offboard path)
- Fix flow INSERT missing labels and lock_error_logs columns (data loss)
- Fix validate_target returning 404 instead of 400 for nonexistent targets
- Fix canSubmit blocking delete when user has no items to reassign
- Fix token preview query filtering out tokens without scopes
- Fix token warning messages: workspace-level mentions webhooks/HTTP triggers,
instance-level mentions API calls using credentials
- Fix "Schedules and triggers" -> "Triggers and runnables" wording
- Show token section at instance level only when tokens exist
- Show Offboard button at instance level when user has no items but deleteUser=true
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add CI test scripts with auto-trigger on deploy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: fix annotation parser early return and handle renames correctly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move CI test results to top of script/flow detail pages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve CI test results spacing, icon, and remove pass label
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: support one-line annotation and use script/path format
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: move CI test trigger logic to EE
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: move CI badge next to New badge and add deduplicated CI summary
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add CI test e2e tests and fix nullable column annotations
Add integration tests for CI test annotation parsing (creates/removes
ci_test_reference rows) and the CI test results API (single + batch
endpoints). Add backend test for auto-trigger on deploy (private+python).
Fix sqlx LEFT JOIN LATERAL nullable column annotations in
get_ci_test_results and get_ci_test_results_batch queries — sqlx
cannot infer nullability from LATERAL subqueries, causing runtime
decode errors when no matching job exists.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix build/sqlx
* fix
* feat: CI test improvements and templates
- Fix windmill-dep-map/private feature propagation in worker, api-scripts,
and api-flows Cargo.toml so CI test triggers actually fire in EE mode
- Clone ci_test_reference rows during workspace fork
- Add polling to CiTestResults component (refetch every 3s while running)
- Add running state and auto-refresh to ForkWorkspaceBanner CI summary
- Add yellow "CI test" badge on script list rows and detail page
- Fix Library badge border color (remove indigo border override)
- Add CI Test TypeScript and CI Test Python templates in ScriptBuilder
- Update sqlx offline cache
- Add debug tracing for CI test trigger in worker_lockfiles
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add missing children prop to WorkspaceDeployLayout
Fixes svelte-fast-check type error when passing named snippets as
children content inside the component tag.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review feedback
- Remove empty wrapper divs around CiTestResults, move mb-4 into component
- Add batch endpoint size cap (max 200 items)
- Add ON DELETE CASCADE to ci_test_reference workspace FK (new migration)
- Downgrade CI test trigger logs from info to debug
- Fix false-positive polling: only treat status='running' as running,
not null status (CiTestResults, CompareWorkspaces, ForkWorkspaceBanner)
- Fix test numbering in integration tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to latest EE commit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to d9d68c2406df0b59f413ea0b2cb24780a9817d04
This commit updates the EE repository reference after PR #516 was merged in windmill-ee-private.
Previous ee-repo-ref: d7ccd9b86da99ec056a0e8708e3637d64290387a
New ee-repo-ref: d9d68c2406df0b59f413ea0b2cb24780a9817d04
Automated by sync-ee-ref workflow.
* fix: treat queued jobs (job_id set, null status) as running
Jobs that have been pushed but not yet picked up by a worker have a
job_id but null status. Treat these as 'running' to avoid showing
misleading 'pass' badges or '0 passing'. Tests that were never
triggered (no job_id, null status) remain neutral/hidden.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: hugocasa <hugo@casademont.ch>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: flow dev page layout and compact toolbar improvements
- Fix JSON.parse error on /flows/dev page when editor not yet initialized
- Increase compact topbar threshold from 700px to 800px
- Reposition "Test flow" button below settings bar when pane is narrow on dev pages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: flow step testing UX improvements
- Store and display logs for step test results (previewLogs in flowState)
- Add logs toggle button in output picker popover
- Fix AI proxy 401 in VS Code extension by passing OpenAPI.TOKEN
- Prevent output picker from closing when clicking Run on same node
- Make toggleOpen idempotent to avoid flicker
- Show loading placeholder in badge area during test execution
- Keep pin button visible during test runs
- Auto-refresh step history when new test completes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: track previous previewJobId to avoid redundant history refreshes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: dev page insert popover z-index, summary editing, output picker UX
- Add #flow-editor portal div to /flows/dev page for correct popover stacking
- Add summary text field at bottom of dev pages when a step node is selected
- Keep pin button visible during test runs
- Show loading placeholder badge to prevent content shift
- Exclude same-node run button from output picker outside-click detection
- Make toggleOpen idempotent when popover already open
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: reuse findModuleInFlow instead of duplicated findModule
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: disable scroll-to-change-number on number inputs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: add comment explaining wheel handler
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multi-word Postgres type names like "double precision" caused the SQL
parser regex to fail (no spaces allowed in type group), falling back to
otyp="text". When Postgres inferred float8 for the column, the
text-typed null couldn't serialize, breaking DB Manager inserts/updates.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix JSON.parse error on /flows/dev page when editor not yet initialized
- Increase compact topbar threshold from 700px to 800px
- Reposition "Test flow" button below settings bar when pane is narrow on dev pages
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: zero-downtime coordinated restarts for OTEL and other setting changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use background_task_state for server heartbeats and fix stale heartbeat detection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show restart propagation toast when saving settings that trigger server restarts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Move ws_specific to separate table
* on delete cascade
* feat: handle ws_specific on resource rename and delete
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* is_false never used
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: resolve esbuild host/binary version mismatch in app sync push
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Revert "fix: resolve esbuild host/binary version mismatch in app sync push"
This reverts commit 8822614f8e.
* fix: update esbuild to 0.28.0 and pin version exactly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: move alert config from config table to global_settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: rename alert setting to alert_job_queue_waiting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add CLI unit tests for pullInstanceConfigs/pushInstanceConfigs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt to merged main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add CLI workspace merge command and enhance fork with datatable/color support
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: abort fork on git branch failure, per-datatable error handling, guard resetDiffTally
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: add fork/merge integration tests covering full cycle
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: support deploying deletions during fork merge (archive/delete in target)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: share deploy logic between CLI and frontend via windmill-utils-internal
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: revert frontend to self-contained deploy, fix failure_module handling
The frontend imports windmill-utils-internal from npm (published v1.3.4)
which doesn't have the new deploy module yet. Revert frontend to its own
self-contained implementation with two improvements:
- Pass failure_module to getAllModules in flow deploy and getItemValue
- Add deleteItemInWorkspace for deploying deletions during merge
The shared deploy.ts in windmill-utils-internal remains for CLI use.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: share deploy logic via published windmill-utils-internal, add comprehensive integration tests
- Publish windmill-utils-internal v1.3.8 with DeployProvider interface
- Frontend now uses shared deploy module (deployItem, deleteItemInWorkspace,
checkItemExists, getOnBehalfOf, getItemValue) via provider adapter
- Add 4 new integration test sub-tests: all item types, secret variables,
special characters, partial deploy + resetDiffTally
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: remove unused folderName function from frontend utils_workspace_deploy
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add scheduled job deletion with configurable retention period
Extends delete_after_use with delete_after_secs to enable configurable
retention periods for job args/result/logs. At completion, jobs can be
scheduled for future deletion via a new job_delete_schedule table,
processed by a monitor task. Supports per-script, per-flow, and
per-flow-step configuration. Backward compatible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add integration tests, revert query! macros, fix review issues
- Add integration tests for resolve_delete_after_secs, schedule_job_deletion,
flow-level and module-level delete_after_secs, backward compat
- Revert sqlx::query() back to sqlx::query!() macros for compile-time safety
- Regenerate sqlx offline cache
- Fix FlowModule/NewScript/FlowValue constructions in all test files
- Fix autoscaling_ee.rs for updated script_path_to_payload return type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt for autoscaling_ee fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: gate cleanup_scheduled_job_deletions behind enterprise feature
Prevents dead_code warning (which CI treats as error via -D warnings)
when compiling without enterprise feature.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate sqlx cache after merge with main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback on scheduled deletion
- Monitor: roll back transaction on any cleanup error so schedule rows
survive for retry on next cycle (instead of best-effort then discard)
- Migration: add FK with ON DELETE CASCADE to job_delete_schedule.job_id
to prevent orphan rows when jobs are deleted through other means
- Simplify bool-to-Option conversion with .then_some(true)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: stop setting delete_after_use alongside delete_after_secs
No mixed-version deployment scenario exists, so delete_after_secs alone
is sufficient. The backend's resolve_delete_after_secs handles
(None, Some(secs)) correctly without needing delete_after_use set.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove delete_after_use from public API surface
Remove delete_after_use from OpenAPI spec, API client, runtime client,
and workspace export. Only delete_after_secs is exposed going forward.
The field remains in Rust backend types with #[serde(skip_serializing)]
for backward-compatible deserialization of existing scripts/flows that
were saved with delete_after_use: true.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 1d4b7a31fc115d6aba8640f7cd3fd5a01abe6806
This commit updates the EE repository reference after PR #519 was merged in windmill-ee-private.
Previous ee-repo-ref: 9eba09a13b778caafc6ae65098b90e53c91984d3
New ee-repo-ref: 1d4b7a31fc115d6aba8640f7cd3fd5a01abe6806
Automated by sync-ee-ref workflow.
* fix: regenerate system prompts, remove unused import
- Regenerate auto-generated system prompts after openflow schema change
- Remove unused serde_json::json import in test file (CI -D warnings)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: insert dummy v2_job row in schedule tests for FK constraint
The job_delete_schedule table has a FK to v2_job, so tests need a
real v2_job row before inserting into the schedule table.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: trigger CI re-run
* fix: remove heavy flow integration tests to avoid CI worker contention
The flow integration tests spawn workers that compete for CPU with
the existing relock_skip tests under --test-threads=10, causing
consistent 60s timeouts in CI. Keep only the lightweight unit tests
and DB integration tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore correct ee-repo-ref for our branch
The ref was overwritten to main's EE ref during a rebase. Restore to
our branch's EE commit that includes the autoscaling tuple fix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: retrigger CI on fresh runner
* fix: remove FK constraint from job_delete_schedule to unblock CI
The FK with ON DELETE CASCADE to v2_job may have caused performance
overhead during test DB setup (each sqlx::test creates a fresh DB
with all migrations). Remove the FK — orphan schedule rows are
harmlessly cleaned by the monitor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ee-ref
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* export_datatable_schema
* Propose to fork the datatable on ws fork
* dump datatable
* Dockerfile
* Fix import_datatable_dump
* datatable schema fork works!
* Option to copy both schema and data
* Datatable fork behavior
* nit ui
* use psql instead
* remove fork_datatable route
* feat: add fork_pg_database and export_pg_schema routes with DB Manager UI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: pluralize "schema" to "schemas" in DB Manager export/import UI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add import mode select (schema only vs schema + data) to DB Manager import
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Select schema or schema+data when important database
* fix: prepend $res: prefix to resource paths in DB Manager import/export
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: dynamic import button label based on selected mode
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* nits
* feat: add warning alert when schema+data import mode is selected
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* nit hide on cloud hosted
* refactor: remove fork_behavior from datatable settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: split CreateWorkspace into layout wrapper and CreateWorkspaceInner
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: instantiate CreateWorkspaceInner in globalForkModal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* nit icons
* Data table fork UI
* feat: pass per-datatable fork behaviors from UI to backend during workspace fork
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix fork overwriting all datatables
* UI nits
* custom instance db refactor
* custom instance db wizard btn for all in dropdown
* nit
* Delete custom instance database button
* Disable forking for resource datatables
* Big import buttons when db empty
* Revert "Disable forking for resource datatables"
This reverts commit 9561cc8fd4.
* feat: add non_diffable flag to resource table
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add resource-type datatable fork with CREATE DATABASE
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: tag forked datatables with nonDiffable and forkedFrom
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: diff datatable and ducklake settings individually on workspace merge
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: skip non_diffable resources and datatables in workspace diff
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: default datatable fork behavior to keep_original
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: make grant permissions non-fatal in instance datatable fork
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: make datatable and ducklake diffs visible in workspace comparison
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: remove datatable fork logic from workspace fork route
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: correct ahead/behind logic for datatable and ducklake diffs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "fix: correct ahead/behind logic for datatable and ducklake diffs"
This reverts commit 6b50884dc6.
* revert: remove datatable and ducklake settings diffing logic
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add datatable clone UI with step-by-step confirmation modal
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: extract datatable fork UI into ForkDatatableSection component
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* nit
* fix: run datatable cloning before workspace fork creation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* nit disable fork admins
* nit fix switching workspace prematurely
* fix: use source workspace for forkPgDatabase calls during fork
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: update forked workspace datatable settings after fork creation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add forked_from field to DataTable and set it for instance forks
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* nit onFinish
* fix: add forked_from to DataTableSettings OpenAPI schema
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: track datatable table DDL changes in workspace_diff
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "feat: track datatable table DDL changes in workspace_diff"
This reverts commit 7526dd68b9.
* feat: add get_datatable_full_schema endpoint and snapshot schema on fork
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix duplicate migration key
* fix: set forked_from on datatable config for both instance and resource types
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* nits
* feat: drop forked databases on workspace deletion with confirmation UI
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: extract drop_forked_datatable_databases from delete_workspace
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: cast pg char columns to text in FK schema query
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: show dbname instead of resource type in fork deletion modal
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* ui nit
* refactor: extract drop_custom_instance_database into windmill-common
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add datatable schema diff section to merge UI
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* UI
* feat: add review drawer with YAML diff and SQL migration runner
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: use Monaco DiffEditor for YAML diff in review drawer
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* nit
* Revert "refactor: use Monaco DiffEditor for YAML diff in review drawer"
This reverts commit a86008ba4c.
* Revert "feat: add review drawer with YAML diff and SQL migration runner"
This reverts commit 0a0deb5ddb.
* feat: add review drawer with DiffEditor and SQL migration runner
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* ui nits
* fix: show diff between forked_from schema and changed side
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: re-fetch target live schema after migration for correct baseline
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* revert
* nit auto next
* feat: add confirmation modal before deploying migration to parent
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: handle missing columns/foreignKeys in schema conversion
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* nits
* refactor: use temp file on disk for pg_dump instead of in-memory string
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Don't replace postgres dbname
* fix: add validation to drop_custom_instance_database and use source db for CREATE/DROP
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: type DataTable.forked_from as DataTableForkedFrom struct
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: simplify fork_pg_database to take source + target_dbname
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* dead code
* feat: enforce schema_and_data admin-only and extract create_custom_instance_database
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: rename fork_pg_database to import_pg_database with source/target/override params
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* nit
* refactor: remove original_dbname/original_resource from forked_from, resolve from parent
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* nit
* fix: resolve forked dbname from fork workspace when dropping resource databases
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* nits
* fix: always clean up global_settings even if database doesn't exist
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: check datatable resource_type from config instead of URL prefix
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: wrap PG default value expressions in braces to prevent CAST quoting
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "fix: wrap PG default value expressions in braces to prevent CAST quoting"
This reverts commit 77f5a2c4e8.
* refactor: reuse columnDefToTableEditorValuesColumn for default value handling
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: store raw API schema in forked_from to avoid double transformation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "fix: store raw API schema in forked_from to avoid double transformation"
This reverts commit e326197a20.
* Revert "refactor: reuse columnDefToTableEditorValuesColumn for default value handling"
This reverts commit bd8f071d9f.
* fix: validate dbname with strict regex to prevent SQL injection
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix default value
* always validate dbname
* refactor: move get_datatable_full_schema structs and logic to query_builders.rs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: split import_pg_database into create_pg_database + import_pg_database
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: extract drop_forked_datatable_databases into its own route
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: require admin when using $res: resource paths in import_pg_database
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use UserDB for $res: resource access and restrict dbname creation
- resolve_pg_source_checked uses UserDB (row-level security) for $res: paths
- transform_json_unchecked is now pub(crate) to prevent misuse
- Non-superadmins can only create databases with wm_fork_ prefix
- datatable:// remains accessible to everyone
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: refuse to drop forked databases unless name starts with wm_fork_
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: remove resolve_pg_source, use resolve_pg_source_checked everywhere
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix permissions
* sqlx prepare
* compilation nits
* sqlx prepare
* sqlx prepare
* wrong route syntax
* fix: allow workspace owner to edit datatable config for fork setup
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "fix: allow workspace owner to edit datatable config for fork setup"
This reverts commit ab683e637b.
* refactor: move datatable fork setup into create_workspace_fork backend
Instead of updating datatable settings from the frontend after fork
creation (which required admin/owner access), pass forked_datatables
info to create_workspace_fork and handle it atomically in the same
transaction. Removes applyPostForkDatatableUpdates from frontend.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: snapshot schema in backend during fork instead of frontend
The schema snapshot is now taken by the backend in apply_forked_datatable
via snapshot_datatable_schema, which connects to the parent workspace's
datatable and runs pg_get_full_schema. This removes the need for the
frontend to call getDatatableFullSchema and pass the schema through.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use get_resource_value_interpolated_internal for $res: to resolve $var: references
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* nit
* sqlx prepare
* fix: add permission check to drop_forked_datatable_databases, validate dbnames, restrict temp file perms
- drop_forked_datatable_databases: same permission as delete_workspace
(fork owner or super admin)
- validate_dbname on target_dbname_override and ForkedDatatableInfo.new_dbname
- Enforce wm_fork_ prefix on forked datatable new_dbname
- DumpFile: set /tmp/windmill/ to 0700 and create files with 0600
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* nit CLI
* Rename to ws_specific
* sqlx prepare
* nit always validate dbname
* fix: include foreign keys in CREATE TABLE migration for added tables
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: detect nextval defaults and use SERIAL/BIGSERIAL types in CREATE TABLE
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update frontend/src/lib/components/DBManagerDrawer.svelte
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* Update backend/windmill-common/src/lib.rs
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* Update backend/windmill-common/src/lib.rs
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* fix: sort foreign keys by constraint name for deterministic schema output
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* sqlx prepare
* rename migration to update timestamp
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* [ee] fix: remove span.enter() in dedicated worker to prevent tracing panic
Update EE ref to include fix for dedicated worker tracing span panic that
caused benchmark failures after ~8000 jobs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 86158dde674238fd94f925bdcd5155759e823ed6
This commit updates the EE repository reference after PR #518 was merged in windmill-ee-private.
Previous ee-repo-ref: a0480130c241d32b7e02951bfb5a03fdfc5737c8
New ee-repo-ref: 86158dde674238fd94f925bdcd5155759e823ed6
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add download all logs button for flow jobs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use recursive CTE to include all nested flow jobs in log download
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: start iteration index at 1 and interleave children with parents
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: distinguish branch vs loop iteration in log section headers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: include flownode and singlestepflow kinds in branch/iteration labels
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve branch labels (branchone: default/1/2, branchall: 1/2)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve module types from flow_node table for nested structures
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use full path in iteration/branch labels and show step kind name
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show iteration index for simple module forloop optimized jobs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: handle aiagent jobs as intermediate flow jobs with tool call children
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: reuse existing get_logs_from_store/disk instead of duplicating
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* sqlx
* sqlx
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore ai agent tool deletion
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: reduce ai tool delete tree walks
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "feat: restore bun for dedicated workers, fix dispatch & serialization, cross-workspace deps (#8645)"
This reverts commit 619ebb65ce.
* feat: accept any content type on webhooks/http triggers with fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Reapply "feat: restore bun for dedicated workers, fix dispatch & serialization, cross-workspace deps (#8645)"
This reverts commit ee5420e401.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add AWS KMS as secret backend (EE)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: switch from AWS KMS to AWS Secrets Manager as secret backend
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add AWS Secrets Manager integration tests (requires LocalStack)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: mark AWS Secrets Manager as beta
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove leftover KMS handler functions from api-settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to include AWS Secrets Manager EE impl
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use full commit hash in ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* sqlx
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Regression test for the missing labels column bug. Creates an app with
a custom path and anonymous execution mode, then fetches it via the
public custom path endpoint.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add path name autocomplete with ghost text and folder cycling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: filter out archived/deleted/draft paths from autocomplete
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show subfolders immediately after Tab-navigating into a folder
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: remove 2-char minimum for suggestions, hide placeholder when suggestions show
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show LCP ghost text for multiple matches, Enter accepts it for Tab cycling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: suppress Path.svelte Enter dispatch when ghost text is accepted
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: compute LCP inline in Enter handler to avoid reactive timing issues
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: Enter picks the first folder and navigates into it
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: Enter picks the currently Tab-highlighted folder, not always the first
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: remove stray blank lines in applyCycleOrComplete
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: review fixes — $bindable default, openapi cache description, non-null assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add sqlx query cache for path_autocomplete
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: log cleanup scans S3 orphans and works cross-server
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: don't skip service log orphan scan when job retention is disabled
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: time-based heartbeat + flag partial folder sizes on list errors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: move background_task module from common to api-settings
Only log_cleanup and storage_usage use it today, both in windmill-api-settings.
Keeping it in the consumer crate narrows the blast radius; if workers or
indexer later need cross-server lease+progress coordination they can move it
back to common then.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add five new attributes to the `job` and `job_postprocessing` tracing spans
so that OTEL-consuming backends (Sentry, Honeycomb, Datadog, etc.) can
filter and group telemetry by how a job was triggered and what type it is.
New span attributes:
- `job_kind` — Script, Flow, AppScript, AIAgent, Preview, etc.
- `created_by` — the user or system identity that queued the job
- `trigger_kind` — schedule, webhook, kafka, http, sqs, etc.
- `trigger` — the schedule/trigger path (when applicable)
- `runnable_id` — the id of the runnable that ran
Also adds `JobKind::as_str()` for a consistent lowercase string
representation, following the same pattern as `ScriptLang::as_str()`.
Existing attributes (job_id, workspace_id, script_path, language, tag,
flow_step_id, parent_job, root_job) are unchanged.
Note: the EE `full_job` span in `otel_ee.rs` and the log records emitted
by `job_logger_ee.rs` would also benefit from these attributes. This PR
covers only the public-repo spans; a follow-up EE change would propagate
the same fields to logs and the full_job span.
Split the DB health page into independent panes so fast pg_catalog-based
diagnostics render without waiting for the slower job table scans, and
enrich the slow queries view with server-side sort, reset, and better
setup guidance.
Backend:
- Split /api/db_health into two endpoints: fast panes (database_size,
connection_pool, table_maintenance, slow_queries, datatables) and
/jobs (job_retention, large_results with scan_limit).
- Add GET /api/db_health/slow_queries?sort=total|mean|calls for
server-side sorting of pg_stat_statements queries (sort whitelisted
via enum, SQL-injection safe).
- Add POST /api/db_health/slow_queries/reset to call
pg_stat_statements_reset().
- Return stats_reset timestamp from pg_stat_statements_info (PG 14+).
- Bump slow queries to top 50 sorted by total_exec_time (was top 10 by
mean_exec_time, which misses high-cumulative-load queries).
- Truncate slow queries to 500 chars (was 200).
- Filter table_maintenance to tables with >= 1000 total tuples.
Frontend (DbHealth.svelte):
- Two tabs (Overview / Jobs) with auto-refresh on selection.
- Refresh buttons right-aligned in both tabs; Jobs tab keeps the
scan_limit selector on the left.
- Job Retention & Large Results always render, with "Click Refresh to
load" placeholders when no data yet.
- Slow queries table: clickable column headers for server-side sort,
click a row to toggle the full query text.
- Reset stats button with confirmation dialog, displays "Stats since"
timestamp for before/after comparison workflow.
- When pg_stat_statements is not installed, show numbered setup
instructions with copyable SQL snippets.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: block adding/inviting members to admins workspace on CE
The admins workspace is reserved for superadmins only. On CE (non-enterprise),
prevent adding or inviting users to it via both API and UI.
Backend: add #[cfg(not(feature = "enterprise"))] guards to invite_user and
add_user endpoints that reject requests targeting the admins workspace.
Frontend: show an info alert on the admins workspace members page and hide
the add/invite/auto-add buttons.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use derived variable for admins workspace alert consistency
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: enforce RLS on $var: resolution in AI proxy to prevent secret exfiltration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update sqlx prepared queries
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: validate AI provider base URLs to prevent SSRF via X-Resource-Path header
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: improve SSRF error message to mention ALLOW_PRIVATE_AI_BASE_URLS env var
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Update clients to check for agent workers
* fixes
* typescript uses 127.0.0.1
* Refresh system prompts
* fix: check both localhost and 127.0.0.1 in workerHasInternalServer detection
Both Python and TypeScript clients now check for both hostnames to avoid
silent breakage if BASE_INTERNAL_URL uses one or the other. Also adds
return type annotation to the Python method.
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
* refresh system prompts
* nit localhost regex boundary
* fix: use provider.language instead of undefined bare language in sqlUtils
The language variable was referenced as a bare identifier in the fetch
calls, resolving to undefined at runtime instead of reading from
provider.language.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The discriminated union type from values.map() wasn't being narrowed by
.filter((info) => !info.raw), causing info.argNum to be typed as
number | undefined instead of number.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* perf: pipeline DISCARD ALL with first query on cached pg connections
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* perf: use RESET ALL instead of DISCARD ALL for lighter session reset
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add integration test for pg session reset on cached connections
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: release MutexGuard before caching so pg connection cache actually works
The old code shadowed the MutexGuard variable without dropping it, so
try_lock() in the post-query caching path always failed — connection
caching was effectively dead code. Restructure to explicitly drop the
guard before connecting.
Also adds a CACHE_HITS counter and clear_pg_cache() helper so the
integration test can verify the cached-connection path is exercised.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add single-worker session isolation test for SET ROLE + search_path
Pushes 3 jobs into the queue before starting the worker so a single
worker processes them all sequentially (matching production). Verifies
SET ROLE and SET search_path do not leak between jobs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add RESET ROLE to session reset (RESET ALL does not undo SET ROLE)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use DISCARD ALL for full session reset and retry on stale connections
- Switch from pipelined RESET ROLE; RESET ALL to eager DISCARD ALL when
validating cached connections. This resets everything: role, GUCs,
prepared statements, temp tables, advisory locks, LISTEN registrations.
- DISCARD ALL also serves as a health check: if it fails, the stale
connection is discarded and a fresh one is created transparently.
- Extract new_pg_connection() helper to avoid duplicating the connect +
spawn-connection-task logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add 100-job single-worker cache stress test
Runs 100 varied PG jobs (plain SELECTs, SET ROLE, SET search_path,
multi-statement) through one worker. Verifies all succeed, 99 hit the
cache, and no session state leaks between jobs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: detect sql.raw() in TS parser and tag queries with has_raw_interpolation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: filter out sql.raw queries from type-checking and preparation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: implement sql.raw() for inline raw SQL fragments in template literals
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: split sqlProviderImpl into provider interface + shared builder
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix ts client compilation
* update asset parser
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add --main flag to write_latest_ee_ref.sh to point to latest EE main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add Azure Key Vault as secret storage backend (EE)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt to azure-key-vault-support branch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add token auth, insecure TLS for emulator, and integration tests
Adds optional `token` field to AzureKeyVaultSettings for direct Bearer
auth (bypasses OAuth2), enables self-signed cert acceptance in token mode,
and includes 4 integration tests against the Azure KV emulator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: handle Azure KV soft-delete and emulator quirks
- Purge soft-deleted secrets after delete to allow name reuse
- Retry set_secret on 409 Conflict (purge stale soft-deleted secret)
- Accept self-signed certs when using static token (emulator mode)
- Work around emulator version-ordering bug in CRUD test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 47b0d9d5d163efdab1e145ee012bdb2eb1373b78
This commit updates the EE repository reference after PR #511 was merged in windmill-ee-private.
Previous ee-repo-ref: d432d78bda151d611d8065162de7c1b7edce92e9
New ee-repo-ref: 47b0d9d5d163efdab1e145ee012bdb2eb1373b78
Automated by sync-ee-ref workflow.
* fix: accept token OR client_secret in Azure KV validation, add token UI field
- isAzureKvConfigValid() now accepts either client_secret or token
- Added token input field to the Azure KV config form for emulator/dev use
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add --main flag to write_latest_ee_ref.sh to point to latest EE main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] feat: add http/protobuf support for OTEL exporters
Add http-proto and reqwest-client features to opentelemetry-otlp to
enable HTTP/protobuf transport as an alternative to gRPC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: expose OTEL protocol selector in instance settings UI
Replace the hardcoded "gRPC" label with a dropdown allowing users to
select between grpc (default) and http/protobuf.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 681b725781611510ed3040c00e8f9b8497d6feda
This commit updates the EE repository reference after PR #509 was merged in windmill-ee-private.
Previous ee-repo-ref: 50051ded8183e662a9e932d87d17258501f3e944
New ee-repo-ref: 681b725781611510ed3040c00e8f9b8497d6feda
Automated by sync-ee-ref workflow.
* fix: remove reqwest-client feature to avoid conflict with default reqwest-blocking-client
The opentelemetry-otlp crate only activates the reqwest-client HTTP client
when reqwest-blocking-client is NOT also enabled. Since the default features
include reqwest-blocking-client, having both resulted in no HTTP client being
created. The default reqwest-blocking-client works correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* iterate
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add secretKeyRef support for jwt_secret and extra fields (rsa_keys)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: update ee-repo-ref to 2c24cf597fdf8c4dccd483f1f1e5c49eb42ef3a3
This commit updates the EE repository reference after PR #508 was merged in windmill-ee-private.
Previous ee-repo-ref: ade3bb76f8e0a6e658313b54c7180577fc9efc37
New ee-repo-ref: 2c24cf597fdf8c4dccd483f1f1e5c49eb42ef3a3
Automated by sync-ee-ref workflow.
* test: replace unit tests with integration tests for jwt_secret and rsa_keys
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: restore bun as default runtime for dedicated workers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add context comment for bun dedicated worker nodejs migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: dedicated worker dispatch for flows + add E2E tests
- Add workspace_id prefix to dedicated worker map lookup keys
- Update ee-repo-ref for dedicated worker path handling fix
- Add spawn_test_worker_dedicated/in_test_worker_dedicated test helpers
- Add 6 E2E tests for dedicated workers:
- test_dedicated_flow_rawscript (regression for "Script not found" bug)
- test_dedicated_flow_workspace_script
- test_dedicated_flow_multiple_steps
- test_dedicated_standalone_script
- test_dedicated_runner_group
- test_dedicated_flow_runners
- Add dedicated_flows.sql fixture with scripts, flows, and worker config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: always run dependency job for dedicated worker scripts
When a script with dedicated_worker=true is deployed with a pre-computed
lock (e.g. via wmill sync push), no dependency job was created, so the
dedicated worker never detected the update and kept running the old version.
Now dedicated worker scripts always generate a dependency job regardless
of whether a lock is provided. The dependency job runs on the dedicated
worker and triggers a restart so it picks up the new script version.
Fixes#8638
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use serial_test for dedicated worker tests to avoid WORKER_CONFIG races
Dedicated worker tests need non-default worker tags in the global
WORKER_CONFIG. When run in parallel (CI uses --test-threads=10),
multiple tests clobber each other's config. Use #[serial] to ensure
dedicated worker tests run sequentially.
Also load worker config from DB via load_worker_config() instead of
manually setting WORKER_CONFIG fields, ensuring consistency with the
monitor's reload path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: nodejs dedicated worker script_path shadowing + add multi-language E2E tests
Fix script_path shadowing in bun_executor nodejs branch where the wrapper
file path was passed to handle_dedicated_process instead of the logical
path, causing "Script not found" for all //nodejs dedicated workers.
Add E2E tests for dedicated flows in all supported languages:
- test_dedicated_flow_deno
- test_dedicated_flow_python
- test_dedicated_flow_bunnative (V8 PrewarmedIsolate path)
- test_dedicated_flow_bun_nodejs (//nodejs annotation)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: simplify dedicated worker dispatch + add serialization and E2E tests
- Unified lookup: always use {workspace}:{runnable_path} for dedicated
worker dispatch, replacing the flow_step_id iteration approach
- Added serialization_semaphore parameter to executor start_worker fns
- Added E2E tests: cross-workspace isolation, conflicting flow step IDs,
preprocessor on dedicated worker
- Added workspace field to RunJob for cross-workspace test support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: cross-workspace workspace dependencies on workers page
Add two new instance-level endpoints to the configs router:
- GET /configs/list_all_workspace_dependencies
- GET /configs/list_all_dedicated_with_deps
Both require devops role and return data across all workspaces,
enabling the workers page to show a consistent view of which
workspace dependencies exist regardless of which workspace the
user is browsing.
Update DedicatedWorkersSelector to use the new cross-workspace
endpoints with fallback to per-workspace calls for non-devops users.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to include dedicated worker lookup simplification
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: use branch name for ee-repo-ref (CI can't fetch by SHA from non-default branch)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update ee-repo-ref.txt with new reference
* sqlx
* fix: revert serialization semaphore, multi-workspace picker, dep conflict warnings
- Remove serialization_semaphore from executor start_worker signatures
- Remove serialization test and fixtures
- Fix DedicatedWorkersSelector to preserve tags from other workspaces
when toggling in the picker
- Track workspace deps per-workspace for conflict detection
- Show warning when dep exists in another workspace but not the script's
- Group runner groups per-workspace to prevent cross-workspace merging
- Add workspace to dep badge link URL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: simplify exec protocol — execd: for single-script, exec: for runner groups
Add execd:/execd_preprocess: commands to bun/deno/python wrappers for
single-script dedicated workers (no path needed). Runner groups keep
exec:/exec_preprocess: with path for multi-script disambiguation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add unit tests for execd:/exec: wrapper protocol
Verify generate_multi_script_wrapper produces both execd: (single-script)
and exec: (runner group) protocol handlers, including preprocessor variants.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update commit reference in ee-repo-ref.txt
* fix: remove beta badge from squash loop, keep tooltip
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update protocol tests to use execd: for single-script wrappers
Deno and bun single-script protocol tests now send execd:{args} instead
of exec:{path}:{args}, matching the updated wrapper protocol. Multi-script
(runner group) tests continue to use exec:{path}:{args}.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unused TEST_SCRIPT_PATH in deno protocol tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: review feedback — down migration, push_as workspace, UI improvements
- Use regexp_replace in down migration for positional accuracy
- Fix push_as() to use self.workspace_id instead of hardcoded value
- Remove per-workspace API fallbacks, use cross-workspace endpoints only
- Skip devops-only API calls when user is not devops (disabled prop)
- Fix duplicate key error for cross-workspace runner groups
- Add workspace to RunnerGroup for unique keying
- Reuse tagRow snippet for standalone items with expand/collapse
- Fix picker alignment: remove empty column for non-expandable items
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: comprehensive dedicated worker test coverage, fix Python execd_preprocess
- Add Python execd_preprocess: handler (was missing for single-script dedicated workers)
- Add 10 E2E tests: flow+standalone conflict, mixed lang fallback, unsupported lang
flow runners, python runner group, bun/python/deno/bunnative preprocessors,
runner group preprocessors, branchone flow
- Add 4 Python unit tests for execd:/execd_preprocess: protocol
- Update EE ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: review feedback — migration escaping, deno try/catch, loadRunnables guard
- Down migration: use E'...' so \n matches actual newlines
- Up migration: anchor regex with ^ to avoid mid-content matches
- Deno execd_preprocess: move JSON.parse inside try/catch
- DedicatedWorkersSelector: skip devops-only API calls when disabled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add dedicated worker relative import tests for bun and python
Verifies that build_loader's CURRENT_PATH correctly resolves workspace-
relative imports when running on a dedicated worker subprocess.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: dedicated worker dispatch for nested flow structures (branches/loops)
- Add extract_flow_root() to strip nesting segments from runnable_path
- Dispatch uses flow_root/flow_step_id for nested paths, runnable_path
for flat paths — deterministic, O(1)
- Fix assert_ran_on_dedicated_worker to BFS all descendants
- Fix python mode labels (python vs python3 for runner groups)
- Add tests: simple forloop, multi-step forloop, whileloop, branchall,
nested branch-in-loop, mixed lang fallback, unsupported lang runners
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: fix ee-repo-ref SHA
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hide picker and skip API calls for read-only users, hide empty runner badge
- Hide "Add more scripts/flows" section when disabled (read-only)
- Skip per-runnable API calls (getScriptByPath, getFlowByPath) for
disabled users — just show path info
- Hide "0 runners" badge on flows with no eligible steps
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 9422b189762ae27edfc346541ae668a4ad728325
This commit updates the EE repository reference after PR #503 was merged in windmill-ee-private.
Previous ee-repo-ref: 4c6ba214bfc23fff05d1dc3200ac59e650af3f4f
New ee-repo-ref: 9422b189762ae27edfc346541ae668a4ad728325
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add --main flag to write_latest_ee_ref.sh to point to latest EE main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve schedule update deadlock by fixing lock ordering in edit_schedule
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add optional labels to scripts, flows, apps, raw apps, schedules, and triggers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update sqlx cache, make labels optional in openapi, regenerate system prompts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add minimal labels input UI to script, flow, and schedule editors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reduce gap between summary and labels input
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add labels to script/flow detail pages and summary/path popover
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move labels inside SummaryPathDisplay trigger for clickable area, reduce gap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: display labels inline to the right of summary, not below
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: increase gap between summary and labels
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add labels to resources/variables, make labels nullable, add home page label filter badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add labels to workspace export/import, resources, variables + test coverage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make migration idempotent, regenerate sqlx cache after merge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: pass labels in script create and flow create/update API calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add labels input UI to resource and variable editors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove negative margin from LabelsInput to prevent overlap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add top and left margin to LabelsInput for better spacing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reduce left margin on LabelsInput
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: widen label input to w-32
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use inline-flex so LabelsInput doesn't stretch full width
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove flex-wrap so label input stays on same line as badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add label filter presets to resources, variables, and schedules search
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use max-w-32 on label input to prevent stretching
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: pull labels closer to summary with negative top margin
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: increase negative margin to pull labels even closer to summary
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: pass labels in schedule create/update API calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use COALESCE to preserve existing labels when not provided in schedule/flow update
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels to CreateResource, EditResource, CreateVariable, EditVariable in OpenAPI spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: display label badges on resource and variable list pages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: display label badges on schedule and all trigger list pages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add folder and label presets to schedules search filter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: apply user_folders_only filter on all workspaces including admins
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add label presets to resources and variables search filters
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: derive folder presets from loaded items, not all workspace folders
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add label query parameter to resource and variable list endpoints in OpenAPI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: display label filter badges inline with folder filters on home page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Revert "feat: display label filter badges inline with folder filters on home page"
This reverts commit 6767a50aa6.
* feat: support comma-separated label filters (allowMultiple) in all list endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: append label presets with comma for allowMultiple filters instead of duplicating key
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hide label presets that are already in the comma-separated filter value
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace unsafe manual SQL ARRAY construction with parameterized queries, add labels to ScriptWDraft
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: complete down migration, add labels to Resource/Variable OpenAPI schemas, remove type cast, add label length validation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels field to Schedule test fixture
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels field to Rust client struct constructions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: regenerate sqlx cache with --all-features for EE builds
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate sqlx cache and package-lock after merge with main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: squash two migrations into one, use IF NOT EXISTS for idempotency
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: track label changes in SummaryPathDisplay to enable save button
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use JSON string comparison for label dirty tracking in popover
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: navigate to script by path after save from popover to load new version
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update initialLabels after save so subsequent label changes enable save again
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use onchange callback for label dirty tracking instead of derived comparison
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reload script by path after label save to fetch new version
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: propagate script/flow labels to jobs at push time
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show script/flow labels on runs page, merge with wm_labels for completed jobs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: change job labels type from JSONB to text[], show labels on job detail page, fix type mismatch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels to QueuedJob struct, fix get_job queries to return v2_job.labels
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace +Label text with icon only
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add tag icon before labels on job detail page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move tag icon inside badge on job detail page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use blue badge with tag icon in RunBadges, remove duplicate labels from JobDetailHeader
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: set icon position to left so tag icon renders in badge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: render Tag icon inline in badge children instead of via icon prop
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: retry icon prop with small badge and position left
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add hover tooltip showing "Label: X" on job label badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: include v2_job.labels in runs page label filter and broad search
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate sqlx cache and system prompts after merge with main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels to EE JobPayload constructions, regenerate sqlx cache with --all-features
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: regenerate sqlx cache CE-only (without EE symlinks that cause conflicts)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update remaining wm_labels JSONB queries to use text[] merge expression
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: simplify job labels to just read v2_job.labels (wm_labels already merged at completion)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: consistent label badge spacing with gap-0.5 wrapper and px-0.5 on badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels: None to test utils JobPayload construction
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add labels to all test fixture JobPayload/NewFlow/EditApp constructions, regenerate sqlx cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: fix vertical content shift by fixing container and input height to h-5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: npm_check errors - unused imports, combinedItems order, flow.labels type, badge px-1 padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unused FolderService imports, fix label badge alignment in RunBadges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore deleted service imports in variables page, remove empty loadFolders
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: trigger CI with updated ee-repo-ref
* chore: update ee-repo-ref to merged EE companion PR
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: trigger fresh CI run for updated ee-repo-ref
* fix: match label badge size with other badges in RunBadges using {large} prop
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove icon from RunBadges label badge to fix vertical alignment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: shorten "Job kind" to "Kind" in run badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add small inline tag icon (10px, -mt-px) to label badge without disrupting height
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add "Label: X" hover tooltip to all label badges, show hidden labels on +N hover
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add tag icon and "Label: X" tooltip to home page label filter badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show LabelsInput even when path is hidden in ResourceEditor
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add labels input to new resource creation drawer (AppConnectInner)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* iterate
* fix: add LabelsInput to all resource creation steps in AppConnectInner
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reduce LabelsInput top margin from -mt-3 to -mt-1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: increase negative margin to -mt-2 for tighter spacing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: split the difference with -mt-1.5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: adjust to -mt-1 for label spacing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: per-site label spacing via class prop instead of global negative margin
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: make label badges clickable to toggle label filter on resources, variables, schedules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use proper array indexOf for label filter toggle, set undefined correctly on removal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use delete instead of undefined to properly clear label filter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add /labels/list endpoint and autocomplete dropdown to LabelsInput
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use inline preventDefault for Svelte 5 event handling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add "Create new" option in label autocomplete, regenerate sqlx cache with update_sqlx.sh
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add GIN indexes on labels column for all 16 tables
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove CONCURRENTLY from GIN index creation in migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add comprehensive label coverage for pull, edit, removal across all item types
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: simplify job label filters to only use v2_job.labels, remove wm_labels back-compat
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add integration tests for job label propagation, display, and filtering
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review findings — missing labels in fetch_script_for_update, app rename, escape key bug
- Add `labels` to SELECT in `fetch_script_for_update` to prevent lost labels on script clone
- Pass `labels` in app branch of `moveRenameManager.ts` so app renames preserve labels
- Clear `inputValue` before `adding = false` in LabelsInput escape handler to prevent accidental label add via onblur
- Fix `test_job_label_filter` to complete jobs via SQL (label filtering only works on completed jobs)
- Add `test_wm_labels_from_result_merged_with_static_labels` integration test using Bun
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hide deprecated cli metadata commands
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: simplify generate-metadata guidance
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: support raw app deployment history
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: refresh deployment history diffs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: preserve deployment history preview context
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: limit deployment history to diffs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: remove unused history backend hook
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Prevent Playwright MCP console logs from being accidentally committed.
Addresses GitHub security advisory for leaked credentials in log files.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add powershell common parameters support (-Verbose, -Debug, -ErrorAction, -WhatIf)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add powershell common params to script editor test panel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: detect CmdletBinding from code instead of schema in script editor
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: ignore commented-out CmdletBinding in powershell detection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use preference variables for -Verbose/-Debug instead of CLI args
Verbose/Debug output goes to PowerShell stream 4/5 which isn't captured
by the 2>&1 redirect. Setting $VerbosePreference/$DebugPreference in the
wrapper scope propagates to child scripts and output flows through the
host to stderr, which Windmill captures as logs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use *>&1 to capture all powershell streams including verbose/debug
The previous 2>&1 only captured error stream. Verbose (stream 4) and
debug (stream 5) output was silently lost. Using *>&1 redirects all
streams to success stream so they flow through Tee-Object into logs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use targeted stream redirects (4>&1 5>&1 2>&1) instead of *>&1
*>&1 breaks $PSCmdlet.ShouldProcess() by redirecting internal streams.
Only redirect verbose (4), debug (5), and error (2) to success stream.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: revert to 2>&1 redirect — stream 4/5 redirects break powershell
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use 4>&1 5>&1 for verbose/debug capture, remove WhatIf support
Stream 4/5 redirects capture verbose/debug in the pipeline. WhatIf is
removed because $PSCmdlet.ShouldProcess() doesn't work when scripts
are invoked through Windmill's wrapper.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: redirect verbose/debug to files to keep result pipeline clean
Verbose (4) and debug (5) streams are redirected to separate log files
during script execution, then output via Write-Host after the script
completes. This keeps them out of the Tee-Object pipeline (used for
result extraction) while still showing them in the job logs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: output verbose/debug to stderr via Console.Error for log capture
Write-Host goes to stdout which gets mixed with result output and
truncated by OSS log threshold. Using [Console]::Error.WriteLine()
writes to stderr which Windmill captures separately as logs, with
VERBOSE:/DEBUG: prefixes for clarity.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: redirect script output to file only, send verbose/debug to stdout
The OSS log storage has a 9KB threshold. Previously, Tee-Object sent
the full JSON result to both stdout (logs) and the pipe file, eating
the log budget. Now script output goes only to the pipe file (> $pipe),
and only verbose/debug messages go to stdout for the log viewer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve original Tee-Object behavior, append verbose/debug after
Keep the original wrapper behavior (Tee-Object to stdout + pipe file).
Only add 4>verbose.log 5>debug.log to capture those streams, and
output them at the end of logs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: inject preference vars into main.ps1 instead of CLI args
Passing -Verbose/-Debug as CLI args causes PowerShell module loading
to emit verbose noise. Instead, inject $VerbosePreference/$DebugPreference
inside main.ps1's try block so they only affect user code. Stream 4/5
are still redirected to files in the wrapper for log output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore common param toggles from previous job args on Run Again
Extract _wm_ps_* keys from loaded args and initialize the toggle
states in PowerShellCommonParams. Also strip them from main args
so they don't appear as unknown schema form inputs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show active common param badges when section is collapsed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: inject ErrorAction as preference variable instead of CLI arg
-ErrorAction as a CLI arg only affects the caller, not the script's
internal error handling. Setting $ErrorActionPreference inside main.ps1
correctly overrides the default 'Stop' behavior for the user's code.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: ensure full backward compatibility with existing powershell scripts
- Only filter common param names when [CmdletBinding()] is present
(without it, $Verbose etc. are regular user-defined parameters)
- Only add 4>verbose.log 5>debug.log and log output lines when common
params are actually enabled — original wrapper is unchanged otherwise
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: lighter styling for common params section
Replaced heavy Section component with a subtle inline chevron toggle
labeled "Common parameters". Smaller text, secondary color, indented
options. Badges still show when collapsed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: rename section to CmdletBinding parameters
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add ..Default::default() to windmill-parser-r (new parser from main)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: missing comma in graphql parser test + merge main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add missing commas before ..Default::default() in parser tests
Merge from main brought test constructors with formatting issues
from the original automated script (missing comma between last field
and ..Default::default()).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore comment markers in nu parser test that script broke
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review — ignore commented CmdletBinding, clear stale params
1. Parser: strip comment lines before detecting [CmdletBinding()] to
avoid false positives from commented-out attributes
2. RunForm: always assign psCommonParams (not just when non-empty) so
stale settings from a previous run don't leak into later runs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The legacyBehaviour parameter on generateFlowLockInternal,
generateAppLocksInternal, and generateScriptMetadataInternal was never
passed as true — the tree parameter alone determines the code path.
Replace `!legacyBehaviour && tree` with just `tree` and remove the
param from all call sites. getRawWorkspaceDependencies keeps its
legacyBehaviour param since it has a real effect there.
Also adds 6 integration tests covering generate-metadata lockfile
generation and idempotency for scripts, flows, and apps.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* perf: re-export GetOptions and GetRange from object_store
Needed by S3 proxy to use get_opts with range for single-request
range fetches instead of HEAD + get_range.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Avoid logging S3 proxy requests as info
* Revert "Avoid logging S3 proxy requests as info"
This reverts commit b6359a7a03.
* Don't log s3 proxy
* Revert "Don't log s3 proxy"
This reverts commit 2b21ee3c78.
* Update duckdb
* AUTOMATIC_MIGRATION for ducklake
* ee repo ref
* wrong comment
* chore: update ee-repo-ref to 41b0d1cb312919109407640fc4bd7060cfe0e107
This commit updates the EE repository reference after PR #505 was merged in windmill-ee-private.
Previous ee-repo-ref: 9b97a1c563365006657c4c6cde6e7df31c5173c3
New ee-repo-ref: 41b0d1cb312919109407640fc4bd7060cfe0e107
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (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: add application-level heartbeat support for websocket triggers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update SQLx metadata
* chore: regenerate auto-generated schema and skill files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: handle missing heartbeat channel gracefully, fix TextInput props
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: only clone heartbeat sender when heartbeat is configured
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: use pre-aggregated worker_group_job_stats for telemetry job usage queries
Replace slow v2_job_completed JOIN v2_job scans with reads from the
pre-aggregated worker_group_job_stats table for the schedule-only
job_usage (48h) and daily_job_usage queries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to telemetry-query-timeout branch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 94567b204a5536ec3dc7591830c58c5bdc1d8381
This commit updates the EE repository reference after PR #506 was merged in windmill-ee-private.
Previous ee-repo-ref: da62a74e965a079d95eea6510f2ac7fc004cdccc
New ee-repo-ref: 94567b204a5536ec3dc7591830c58c5bdc1d8381
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add opt-in SMTP click tracking disable for email links
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt for email clicktracking branch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt after simplification
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: exclude trailing commas from URL regex in clicktracking
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 57dd88faa3b0b354f813385cf3f6a34eca54a4a1
This commit updates the EE repository reference after PR #504 was merged in windmill-ee-private.
Previous ee-repo-ref: 5cf901db7fb0ea169b09564372e444f28e23ac3a
New ee-repo-ref: 57dd88faa3b0b354f813385cf3f6a34eca54a4a1
Automated by sync-ee-ref workflow.
* chore: update ee-repo-ref.txt to include dedicated worker fixes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: restore bun as default runtime for dedicated workers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add context comment for bun dedicated worker nodejs migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: dedicated worker dispatch for flows + add E2E tests
- Add workspace_id prefix to dedicated worker map lookup keys
- Update ee-repo-ref for dedicated worker path handling fix
- Add spawn_test_worker_dedicated/in_test_worker_dedicated test helpers
- Add 6 E2E tests for dedicated workers:
- test_dedicated_flow_rawscript (regression for "Script not found" bug)
- test_dedicated_flow_workspace_script
- test_dedicated_flow_multiple_steps
- test_dedicated_standalone_script
- test_dedicated_runner_group
- test_dedicated_flow_runners
- Add dedicated_flows.sql fixture with scripts, flows, and worker config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: always run dependency job for dedicated worker scripts
When a script with dedicated_worker=true is deployed with a pre-computed
lock (e.g. via wmill sync push), no dependency job was created, so the
dedicated worker never detected the update and kept running the old version.
Now dedicated worker scripts always generate a dependency job regardless
of whether a lock is provided. The dependency job runs on the dedicated
worker and triggers a restart so it picks up the new script version.
Fixes#8638
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use serial_test for dedicated worker tests to avoid WORKER_CONFIG races
Dedicated worker tests need non-default worker tags in the global
WORKER_CONFIG. When run in parallel (CI uses --test-threads=10),
multiple tests clobber each other's config. Use #[serial] to ensure
dedicated worker tests run sequentially.
Also load worker config from DB via load_worker_config() instead of
manually setting WORKER_CONFIG fields, ensuring consistency with the
monitor's reload path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: nodejs dedicated worker script_path shadowing + add multi-language E2E tests
Fix script_path shadowing in bun_executor nodejs branch where the wrapper
file path was passed to handle_dedicated_process instead of the logical
path, causing "Script not found" for all //nodejs dedicated workers.
Add E2E tests for dedicated flows in all supported languages:
- test_dedicated_flow_deno
- test_dedicated_flow_python
- test_dedicated_flow_bunnative (V8 PrewarmedIsolate path)
- test_dedicated_flow_bun_nodejs (//nodejs annotation)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: simplify dedicated worker dispatch + add serialization and E2E tests
- Unified lookup: always use {workspace}:{runnable_path} for dedicated
worker dispatch, replacing the flow_step_id iteration approach
- Added serialization_semaphore parameter to executor start_worker fns
- Added E2E tests: cross-workspace isolation, conflicting flow step IDs,
preprocessor on dedicated worker
- Added workspace field to RunJob for cross-workspace test support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: cross-workspace workspace dependencies on workers page
Add two new instance-level endpoints to the configs router:
- GET /configs/list_all_workspace_dependencies
- GET /configs/list_all_dedicated_with_deps
Both require devops role and return data across all workspaces,
enabling the workers page to show a consistent view of which
workspace dependencies exist regardless of which workspace the
user is browsing.
Update DedicatedWorkersSelector to use the new cross-workspace
endpoints with fallback to per-workspace calls for non-devops users.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to include dedicated worker lookup simplification
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: use branch name for ee-repo-ref (CI can't fetch by SHA from non-default branch)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update ee-repo-ref.txt with new reference
* sqlx
* fix: revert serialization semaphore, multi-workspace picker, dep conflict warnings
- Remove serialization_semaphore from executor start_worker signatures
- Remove serialization test and fixtures
- Fix DedicatedWorkersSelector to preserve tags from other workspaces
when toggling in the picker
- Track workspace deps per-workspace for conflict detection
- Show warning when dep exists in another workspace but not the script's
- Group runner groups per-workspace to prevent cross-workspace merging
- Add workspace to dep badge link URL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: simplify exec protocol — execd: for single-script, exec: for runner groups
Add execd:/execd_preprocess: commands to bun/deno/python wrappers for
single-script dedicated workers (no path needed). Runner groups keep
exec:/exec_preprocess: with path for multi-script disambiguation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add unit tests for execd:/exec: wrapper protocol
Verify generate_multi_script_wrapper produces both execd: (single-script)
and exec: (runner group) protocol handlers, including preprocessor variants.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update commit reference in ee-repo-ref.txt
* fix: remove beta badge from squash loop, keep tooltip
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update protocol tests to use execd: for single-script wrappers
Deno and bun single-script protocol tests now send execd:{args} instead
of exec:{path}:{args}, matching the updated wrapper protocol. Multi-script
(runner group) tests continue to use exec:{path}:{args}.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unused TEST_SCRIPT_PATH in deno protocol tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add LIMIT_WINDOWS_TO_1CU env var for Windows worker memory limits
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CI review — stricter env var parsing and SAFETY comment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: generate-metadata non-interactive CI and misleading log path
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add hash consistency tests for workspace deps staleness checks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve race condition where flow sync push reverts to stale version
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add sqlx offline cache for new queries
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add version guard before writing to prevent TOCTOU race
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add endpoint to restart workers in a worker group
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate sqlx query cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add missing modules field to RawCode in tests and regenerate sqlx cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* update sqlx
* fix: use require_devops_role for restart worker group endpoint
Matches the permission level of the clean cache endpoint (update_config),
allowing both superadmin and devops role users.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback for restart worker group
- Fix OpenAPI description to say "devops role" instead of "superadmin"
- Add dispatch('reload') after restart to refresh worker list
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: only dispatch reload on successful restart
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve bun bundle error message for syntax errors like unclosed brackets
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* revert: remove error hint from node_builder.ts wrapper catch blocks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: respect disabled fields in JSON input mode
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: guard against undefined default in disabled field enforcement
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show toast when disabled fields are reset to defaults on run
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add Entra ID (Azure Workload Identity) support for database auth
Add support for Azure Workload Identity to authenticate to Azure Database
for PostgreSQL using short-lived Entra ID tokens. Mirrors the existing
AWS IAM RDS auth pattern.
- Extract shared DatabaseParams to db_params.rs for reuse across providers
- Add DatabaseUrl::EntraId variant with token refresh
- Detect "entraid" magic password in DATABASE_URL
- Unified background refresh task for both IAM RDS and Entra ID
- Support sovereign clouds via AZURE_AUTHORITY_HOST env var
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore needs_refresh() check in background token refresh task
The unified refresh task was missing the needs_refresh() gate, causing
it to refresh tokens every 10 seconds instead of only when near expiry.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt for Entra ID branch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move entraid env var reads inside cfg(private) block
Fixes unused variable warnings in OSS and EE-without-private builds
where -D warnings is enabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 0e001bab643e449b3310b0692dd3598ee0902ecc
This commit updates the EE repository reference after PR #483 was merged in windmill-ee-private.
Previous ee-repo-ref: 44199013ed0c96680672e718f35124aa34a5d010
New ee-repo-ref: 0e001bab643e449b3310b0692dd3598ee0902ecc
Automated by sync-ee-ref workflow.
* refactor: add needs_refresh() and refresh_if_needed() to DatabaseUrl
Simplify duplicated refresh logic per Claude review suggestion.
Background task and get_database_url() now use shared methods
instead of matching on each variant individually.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Add a migration that runs just before 20260318000000 (add_permissioned_as).
For each trigger table, if the email column still exists, update edited_by
to the trigger's email when the user is not in the workspace but is a
superadmin. This ensures the subsequent permissioned_as migration stores
the raw email instead of an invalid u/{username} reference.
If 20260318000000 was already applied, the migration is a no-op (email
column is gone, guarded by information_schema check).
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: correct raw app flow inputs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: remove raw app legacy migration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: strip f/ prefix from folder paths when deploying from workspace forks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract folderName helper for f/ prefix stripping
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: validate rd redirect on login with same rules as logout
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: sanitize rd at source in login callback to prevent leaking to goto
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: validate rd redirect in Login component for fresh login flow
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add R language support
Add R as a new supported scripting language in Windmill, following the
same pattern used for Ruby. Includes:
- Backend: ScriptLang::Rlang enum variant, DB migration, tree-sitter-r
parser crate with tests, WASM parser binding, R executor with NSJail
sandboxing, job dispatch and signature parsing
- Frontend: language picker, R icon, syntax highlighting, editor bar
insertions (Sys.getenv, get_variable, get_resource), schema inference,
init code template, BETA badge
- CLI: .r extension mapping, sync support, bootstrap template
R scripts use `main <- function(...)` syntax, jsonlite for JSON
serialization, and system curl for the Windmill client helper.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add R package resolution and installation
Parse library()/require() calls from R scripts to extract dependencies.
Resolve versions from CRAN, cache lockfiles in pip_resolution_cache,
and install packages to a shared R library cache. The run step sets
R_LIBS_USER so installed packages are available to the script.
- Parser: parse_r_requirements() extracts package names from AST
- Executor: resolve() generates lockfile, install() installs from CRAN
- Worker lockfiles: wire up R resolve for dependency jobs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add nsjail sandboxing for R resolve and install phases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: fix R get_variable/get_resource and add sandbox annotation + e2e tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: fix R arg inference with JS fallback parser and get_variable/get_resource
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix flake
* nsjail
* nits
* fix: R install improvements - suppress verbose output, flat lockfile logging, Dockerfile R support, rlimits
- Suppress renv verbose output during resolve and install (controlled by #verbose annotation)
- Filter renv from install list (already loaded, causes noisy restart message)
- Log compact "resolved N packages" instead of full renv.lock JSON
- Add R (r-base, r-cran-renv) to DockerfileFull and DockerfileFullEe
- Use disable_rl for nsjail install config (R compiles from source)
- Reduce default concurrency from 20 to 5
- Add rlang to openflow.openapi.yaml
- Fix MainArgSignature (no_main_func -> auto_kind) after main merge
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* final
* fix: remove accidental R install from multiplayer Dockerfile
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: remove R from Windows build and DockerfileExtra
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: rename R migration to avoid timestamp collision with trigger_filter_logic
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* all
* fix: R install improvements - suppress verbose output, flat lockfile logging, Dockerfile R support, rlimits
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: add clear error when Rscript binary is missing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: fix type errors in R fallback parser, use format! in wrap(), add R system prompts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: pyranota <pyra@duck.com>
* fix: prevent browser freeze when approval form number field has no default value
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: disable approval buttons and keep polling after approve/deny action
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: restore approval page link and prevent double resume in flow viewer
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: guard against NaN fallback in Range and reset actionTaken on new approval step
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix approval page url
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use fully qualified tmux pane targets in webmux systemPrompt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: anchor tmux pane targets to $TMUX_PANE for stability across window switches
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add configurable preview job tag override in default tags settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: skip re-tagging for FlowPreview jobs when preview override is active
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: support hub flows in raw app runnables
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: support hub flow previews in app ui
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: move trigger context into flow graph viewer
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use script viewer for hub flow steps
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: stretch raw app flow previews to pane height
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: improve hub flow run links
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: stabilize hub flow preview drawer
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: align hub flow id validation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* style: fix runnable panel indentation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: improve CLI flow log streaming, sub-job listing, and failure handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add hierarchical flow status in job get and aggregated flow logs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove duplicate ansi color hint in job logs output
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update cli-commands skill with new job/flow features
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add integration tests for flow job inspection and log aggregation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove internal friction discovery doc from branch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: trim cli-commands skill to reduce context bloat
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update job command descriptions and regenerate skills.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: commit auto-generated files from system_prompts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review comments on flow streaming and test assertions
- Move for-loop waiting logic outside --silent guard (Cubic #2)
- Break outer loop when for-loop module fails (Cubic #3)
- Strengthen test assertion: toContain("a") -> toContain("a: Generate data") (Cubic #1)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: generator regex truncating descriptions with parentheses
The .command() regex used [^)]+ for the second arg, stopping at the
first ')' inside description strings like "(machine-friendly)".
Now matches quoted strings properly before falling back.
Fixes 6 truncated descriptions across job, flow, and script commands.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: support sensitive/secret fields for non-string types
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: restrict sensitive toggle to object type, move after showExpr
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show sensitive toggle in PropertyEditor at bottom, after children
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: gate sensitive toggle with showSensitiveToggle prop
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: process secret args in flow test and script test paths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: inline SecretArgInput into ArgInput, delete component
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CI review feedback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: pass showSensitiveToggle to flow input schema editors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use explicit prop syntax to satisfy svelte-check
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: narrow try/catch to only processSecretArgs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: 5x request size limit for raw app bundle uploads
Raw app bundle endpoints (create_raw, update_raw) now get 5x the
configured request size limit. Also improves error messages when
multipart uploads exceed the limit to include the actual limit
and mention it's adjustable in instance settings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: mention size limit as possible cause, not definitive
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: support multiline secrets in resource password fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: wire minRows through to password textarea instead of disabling it
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: auto-detect multiline in password field instead of always using textarea
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: switch to textarea on Enter keypress in password field
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: focus textarea after switching from single-line password input
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: simplify Password multiline logic and fix cursor position bug
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: set 300s timeout on python client httpx to prevent ducklake query timeouts
The httpx.Client was using the default 5s timeout, causing ducklake SQL
queries (which run synchronously via run_inline_preview_script) to timeout
for any query taking longer than 5 seconds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: disable timeout on python client httpx
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update client.py
* Update client.py
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: compute highest workspace role across all instance groups when adding user to group
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add regression tests for instance group role precedence
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 15dd361793564ab50bc485b71b042da4cfc1d32c
This commit updates the EE repository reference after PR #501 was merged in windmill-ee-private.
Previous ee-repo-ref: 7e9d0e7ec0b52585cab368b7351e3a5654da3589
New ee-repo-ref: 15dd361793564ab50bc485b71b042da4cfc1d32c
Automated by sync-ee-ref workflow.
* refactor: extract compute_highest_workspace_role helper and clean up role logic
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to e08a87450627bef9013498e40ee93a47bedda7ee
This commit updates the EE repository reference after PR #502 was merged in windmill-ee-private.
Previous ee-repo-ref: a4ba20109813332320839488da5ecf83ca8f70b4
New ee-repo-ref: e08a87450627bef9013498e40ee93a47bedda7ee
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: expose getJob and getJobLogs as MCP tools
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add integration test for getJob/getJobLogs MCP endpoint tools
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add MCP client integration test for getJob and getJobLogs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add OR logic support to kafka/websocket trigger filters
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref for OR logic filter support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add filter_logic to OpenAPI spec/save utils, fix websocket derive, show capture group ID
- Add filter_logic field to all 6 Kafka/WebSocket OpenAPI schemas so it
is included in the generated frontend client types
- Include filter_logic in save request bodies (kafka/utils.ts, websocket/utils.ts)
- Fix misplaced #[derive(FromRow)] on WebsocketConfig (was on the default fn)
- Show copyable "Test group ID" in Kafka capture UI
- Remove capture event-loss warning for Kafka (uses separate consumer group)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* update sqlx
* update ee ref
* chore: regenerate system prompts for filter_logic schema changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove banned $bindable(default_value) pattern in TriggerFilters
Use $bindable() without default and $derived with ?? for the effective
value, per CLAUDE.md rules.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make filterLogic prop required in TriggerFilters
All callers always pass it, no need for optional + derived fallback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 5ee1382dfb23b6a1516e3c7586058cec8240fdf2
This commit updates the EE repository reference after PR #498 was merged in windmill-ee-private.
Previous ee-repo-ref: bbd674991c07bff1cb2f3744e71fda10df53f09d
New ee-repo-ref: 5ee1382dfb23b6a1516e3c7586058cec8240fdf2
Automated by sync-ee-ref workflow.
* fix: reset filterLogic to 'and' in openNew for kafka/websocket editors
Prevents stale OR logic from carrying over when creating a new trigger
after editing one with OR filters.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: hugocasa <hugo@casademont.ch>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Prevents excessive memory/query load from large per_page values on the
inputs/history endpoint in cloud environments.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add plain_emails_telemetry notification handler
Listen for plain_emails_telemetry setting changes via the global settings
notification channel, matching the workspace_telemetry_enabled pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 780857855e231c9d71f02fefd8253c254542ef32
This commit updates the EE repository reference after PR #500 was merged in windmill-ee-private.
Previous ee-repo-ref: 393c9c4ebd317d09466866ab17cc1ee9fc25582a
New ee-repo-ref: 780857855e231c9d71f02fefd8253c254542ef32
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: WAC workflow diagram visualization in script editor
Add WASM-powered workflow diagram for WAC scripts in the script editor,
inspired by Cloudflare's workflow diagrams approach. Parses WAC code
client-side via WASM and renders an interactive DAG using @xyflow/svelte.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show WAC diagram on script detail page
Show the workflow diagram below the run form on the script detail page
for WAC scripts, matching how flows display their graph.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: handle try/catch and while loops in WAC diagram
Instead of rejecting these patterns with validation errors, render them
as graph nodes:
- try/catch → Branch node with "try"/"catch" edge labels
- while loops → LoopStart/LoopEnd with condition as iter_source
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove dead code from WAC parser and add pkg-wac to publish script
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: use published windmill-parser-wasm-wac@1.668.5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: handle undefined language prop in WacDiagram usage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve windmill-parser-wasm-wac from npm registry in lockfile
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: create actual merge nodes for branch/try-catch convergence points
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* perf: enable S3 bundle cache for PHP previews without lock file
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: resolve borrow-after-move of lock in php cache save
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: use DB-based lockfile cache for PHP previews instead of requirements-only key
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: prevent stale lockfile TTL refresh in PHP preview cache
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: add sqlx offline cache entry for PHP lockfile resolution query
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
fallback_service on a router with no explicit routes is invisible to
axum's nest() — requests never reach the nested fallback, resulting in
404s. route_service("/", service) registers an actual route so nest()
forwards correctly.
Also reverts layer back to route_layer for the ApiAuthed extractor
since there is now a real route to match against.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DuckDB's information_schema.columns returns IS_NULLABLE as a boolean,
but the ColumnDef struct expects a string ('YES'/'NO'). This caused
deserialization to fail with "invalid type: boolean, expected a string"
when expanding WM_INTERNAL_DB markers (e.g. COUNT) for Ducklake tables.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: widen login rate limit defaults to reduce false positives
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: increase rate limits further for large-scale deployments
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: harden login rate limiting with CLOUD_HOSTED gating, memory eviction, and race fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace global DashMap with atomics and move extract_client_ip inside conditional
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): phantom diffs, flow push safety, error messages, digest stability
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): differentiate stale vs missing metadata warnings on script push
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): job list --limit off-by-one, deps push double error
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): flow get shows nested steps, lint works on specific directories
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cli): add lint --watch mode for continuous validation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): email trigger template missing local_part, trigger get shows all fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): fix CI — flow push warns instead of failing, lint subdir detection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): app push crash, lint entry point, push --message, run arg validation, history timestamps
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): update sqlx cache and fix second history query missing created_at
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(cli): regenerate system prompts after new CLI options
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): fix 13 CLI bugs — exit codes, sync tar fallback, variable encryption, JSON output, parent dirs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): address PR review — TarAsZip.folder(), retry timeout, stderr hint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): update resource-type list test to handle empty state message
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add DB health diagnostic dashboard for superadmins
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update SQLx metadata
* fix: improve db health query performance
Bound large_results scan to last N jobs (configurable via scan_limit
query param, default 10K) instead of full-table pg_column_size sort.
Replace N+1 datatable size queries with single batched pg_class lookup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update SQLx metadata
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* sqlx
---------
Co-authored-by: Claude Opus 4.6 <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(cli): add job, group, audit, token commands and schedule enable/disable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(cli): regenerate system prompts after new commands
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): address PR review feedback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(cli): regenerate system prompts after review fixes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(cli): extract shared formatTimestamp util and remove unused resolveWorkspace in token
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace label with div for filter value editor to fix focus stealing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 02c0d34e54e71c9293f9cefb56f68652cf0db8a5
This commit updates the EE repository reference after PR #497 was merged in windmill-ee-private.
Previous ee-repo-ref: 44d665af35ad23cd3549b1d094f5d6633237deb4
New ee-repo-ref: 02c0d34e54e71c9293f9cefb56f68652cf0db8a5
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(cli): address 28 DX friction points across CLI commands
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore(cli): regenerate system prompts after help text updates
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): address PR review feedback
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): update removeType tests to match lenient behavior
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): address CE/EE sync friction and improve JSON output
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): revert instance config masking to avoid breaking push flow
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): mask instance secrets by default with interactive prompt
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore(cli): regenerate system prompts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): use stderr for errors, optimize skipped-files scan, rename --auto to --auto-metadata
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(cli): improve workspace fork lifecycle — delete-fork fallback, list-forks, --workspace override
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): update fork merge instructions to reference all merge methods
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): clarify skipped-files warning comment re DynFSElement traversal
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* [ee] fix: update ee-repo-ref to fix deprecated rand API in CI
Updates ee-repo-ref.txt to point to a commit that replaces deprecated
rand::thread_rng().gen() with rand::rng().random() in the MITM proxy
cert generation, fixing the check_ee_full CI failure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 9316adc693d7f1a668df661e000109bb48b93375
This commit updates the EE repository reference after PR #495 was merged in windmill-ee-private.
Previous ee-repo-ref: d311a3c6ecb50c086fb86b1f4fa3f9e62ff40df5
New ee-repo-ref: 9316adc693d7f1a668df661e000109bb48b93375
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* test: add x509-parser dev-dep for MITM proxy cert tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt for ssl-verify-fix branch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to a90b083660b372bf1da1c18769cbd50936ea8040
This commit updates the EE repository reference after PR #494 was merged in windmill-ee-private.
Previous ee-repo-ref: db665a09d5b9a485977d73c22908629e3dda6200
New ee-repo-ref: a90b083660b372bf1da1c18769cbd50936ea8040
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add IAM RDS auth support for PostgreSQL worker resources
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: use Config builder for IAM RDS connections
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review feedback for IAM RDS auth
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to ebea6ef1e5bfcfc3f0151da9687dac6c61bbfab6
This commit updates the EE repository reference after PR #493 was merged in windmill-ee-private.
Previous ee-repo-ref: 1228561a98c5195bb97a81d4a57ce2bb2ecfca79
New ee-repo-ref: ebea6ef1e5bfcfc3f0151da9687dac6c61bbfab6
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(cli): preserve inline script files during flow generate-locks
Three bugs caused `wmill flow generate-locks` to destroy inline script
content and rename files:
1. YAML parser stripped unquoted `!inline` tags (treated as YAML tag,
not string prefix), leaving just the filename as script content.
Fix: register custom YAML tags for `!inline` and `!inline_fileset`.
2. Inline script files were renamed based on step summaries because
`extractInlineScriptsForFlows` was called with empty mapping `{}`.
Fix: call existing `extractCurrentMapping()` before replacement and
pass the mapping to preserve original filenames.
3. Lock file paths were derived from the assigner instead of the mapped
content path, causing inconsistent naming.
Fix: derive lock base path from mapped content path when available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(cli): add unit tests for !inline YAML tag and mapping preservation
- YAML tag tests: unquoted/quoted !inline parsing, !inline_fileset,
nested structures, round-trip stability
- Mapping tests: path preservation with mapping, fallthrough without
mapping, lock path derivation from mapped content path, mixed
mapped/unmapped modules, dotted path handling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): correct yaml parse type cast and inline prefix check
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): harden lock path for extensionless files and merge customTags
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* improve logging for github app operations
* ee ref
* chore: update ee-repo-ref to 0b9e92f9e089293c6d523b77ed2c11edbc7a99c0
This commit updates the EE repository reference after PR #489 was merged in windmill-ee-private.
Previous ee-repo-ref: b259642e7f36b83a991034d5b28ae616f94ee5fc
New ee-repo-ref: 0b9e92f9e089293c6d523b77ed2c11edbc7a99c0
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* test: add OTEL coverage tests
Add 38 unit tests covering OpenTelemetry infrastructure:
- OtelSettings serde (empty, partial, full, roundtrip, skip_serializing)
- OtelTracingProxySettings serde (defaults, languages, dedup, rejection)
- ScriptLang rename cases
- LogCounter initialization and CountingLayer event counting
- Targets filter suppression of windmill:job_log
- get_otel_context_envs traceparent format verification
- Worker OtelTracingProxySettings (HashSet variant)
Companion EE PR adds tests for span_cx_from_job_id, metric functions,
proto conversion, SpanBuilder, and tracing proxy handler.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add E2E OTEL tests with in-memory exporters
Add integration tests that verify metrics and spans flow correctly
through the OpenTelemetry pipeline using in-memory exporters:
Metrics (1 comprehensive test):
- All 20 metric names registered correctly
- Counter values (push/delete/pull/zombie/execution/failed/started)
- Gauge values with attributes (queue count by tag, worker busy, db pool, health)
- Histogram values (execution duration, pull duration)
- Health status phase encoding (healthy=1, degraded=0, unhealthy=0)
Spans (6 tests):
- Root job span created with "full_job" name and Ok status
- Error status with "Job failed" description on failure
- trace_id derived from job UUID
- span_id derived from job UUID low bits
- Child jobs (with parent_job) produce no span
- Attribute values (job_id, workspace_id, script_path) match job data
Also:
- Add testing feature to opentelemetry_sdk for InMemoryMetricExporter
- Update otel_oss.rs for SdkTracer type rename in 0.30
- Add opentelemetry/opentelemetry_sdk to dev-dependencies
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove unit tests in favor of E2E OTEL tests
The E2E integration tests in backend/tests/otel.rs cover the same
ground more thoroughly with in-memory exporters.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] fix: update ee-repo-ref for dedicated worker job_dir fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] fix: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 5e8b1bcfc2c9ade9db39c839f2faed4f82da5efc
This commit updates the EE repository reference after PR #490 was merged in windmill-ee-private.
Previous ee-repo-ref: d958cd3b8a9a17b5f3cb6cb411c8ebba0c380fdd
New ee-repo-ref: 5e8b1bcfc2c9ade9db39c839f2faed4f82da5efc
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(cli): separate unit tests from integration tests and fix test cleanup
- Rename 14 non-backend test files to *_unit.test.ts convention
- Add UNIT_ONLY env var guard in setup.ts to skip cargo build/backend startup
- Add test:unit and test:integration scripts to package.json
- Use setsid on Linux for process group management so stop() kills both
cargo and the windmill child process
- Fix exit handler to kill process group instead of just the direct child
- Add cleanupStaleTestResources() to drop orphaned windmill_test_* databases
and kill orphaned backend processes on startup
- Rewrite TESTING.md with current bun-based instructions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): fix process group approach - kill by db name instead of setsid
The setsid approach didn't work because setsid forks, making the PID
we get from Bun.spawn ephemeral. Instead, kill orphaned windmill child
processes by matching our unique database name in /proc/pid/environ.
Also add afterAll hook in setup.ts so full async cleanup (process kill
+ database drop) runs when all tests complete normally, not just on
SIGINT/SIGTERM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): address PR review feedback
- Remove duplicate cleanupStaleTestResources() call in getTestBackend()
(already called in setup.ts)
- Add regex guard on database names before SQL interpolation
- Extract shared killWindmillProcessesByEnvMatch() helper to deduplicate
process-killing logic
- Remove redundant test:integration script (test already runs everything)
- Flip setup.ts to if/else pattern for readability
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add DB-coordinated graceful restart staggering for settings changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve original instance names in restart coordination record
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove randomness, add drain delay for in-flight requests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: spawn restart in background, deduplicate entries, clarify stale filter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WAC v2 scripts previously disabled bundle caching, forcing every execution
to resolve windmill-client from node_modules at runtime (~74ms overhead per
bun launch). This makes both the prebundle and execution paths WAC-aware by
including WorkflowCtx/StepSuspend/setWorkflowCtx re-exports in the bundle,
so the wrapper can import them from the cached bundle instead of node_modules.
Benchmarked improvement: wac_inline_2 12→38 wf/s (3.2x), wac_seq_2 6→17 wf/s
(2.8x) with no regression on plain bun scripts or flows.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: support multiple folder selection in MCP scope selector
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add per-folder caching for multi-folder runnables loading
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review — workspace prop, length check, empty folder state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: cache folder names per workspace and reload on workspace change
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve notes on nodes inside collapsed groups
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hide notes for nodes inside collapsed groups instead of repositioning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, windmill.queue.count and windmill.queue.running_count OTEL
metrics would report no data instead of 0 when a tag's queue emptied.
This was because the SQL query uses GROUP BY tag, so empty tags are
absent from results. The Prometheus path already handled this by tracking
previously-seen tags and emitting 0, but the OTEL path was missing this
logic.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: handle inline script file deletions in app/flow folders during sync push
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add regression test for app inline script deletion during sync push
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: flow new respects nonDottedPaths setting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add flow new nonDottedPaths test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: separate stat from pushObj in delete handler to avoid masking errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: sanitize flow step summaries for filesystem-safe names
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: bump windmill-utils-internal to 1.3.6
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: handle Windows reserved device names in flow step sanitization
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: collapse consecutive underscores in sanitized flow step names
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: bump windmill-utils-internal to 1.3.7
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* bump
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: generate commented wmill.yaml template and add config reference command
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add missing options to config reference (promotion, skipBranchValidation, commonSpecificItems)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: generate YAML template from CONFIG_REFERENCE instead of handwritten string
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve YAML comments when binding workspace profile during init
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: simplify to `wmill config` and reorder table columns
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: generate JSON Schema for wmill.yaml editor autocomplete and validation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove redundant templateValue fields and make specificItemsSchema data-driven
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: use native JSON Schema types in CONFIG_REFERENCE, strip non-schema keys for generation
Eliminates typeToJsonSchema, specificItemsSchema, codebaseItemSchema,
branchConfigSchema, and the complex generateJsonSchema body. Each
CONFIG_REFERENCE entry is now a JSON Schema property with extra metadata.
Schema generation just iterates and strips non-schema keys.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove typeLabel and displayType — use schema types directly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove hidden entries, auto-expand nested schemas in reference table
Sub-fields (codebases[], gitBranches.<branch>.*) are now derived from
the parent's inline schema instead of being maintained as duplicate
hidden entries. Removes 29 entries and the hidden field entirely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use console.log for JSON output and quote YAML-special branch names
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts to include new config command
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: review feedback + add tests for template, schema, and config reference
- Use console.log for --json output (no ANSI escape codes)
- Quote branch names with YAML-special characters
- Add 28 tests covering template generation, JSON Schema validation,
config reference formatting, and CONFIG_REFERENCE integrity
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add generate-schema script and commit wmill.schema.json to repo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove schema.json generation from wmill init
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: eliminate read-back cycle, harden yamlKey, fix triple negation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: upgrade axum 0.7 to 0.8 and related dependencies
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add route reachability tests for ~80 previously untested endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: switch feature-gated trigger handlers from axum::async_trait to async_trait crate
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update new trash routes to axum 0.8 path syntax
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to latest EE commit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: upgrade route tests to assert 2xx responses with proper data setup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: restore npm_proxy and ai_routes tests using local echo servers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: gate workspace fork test behind enterprise feature flag
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add ~40 more endpoint tests (jobs authed, health, favorites, ACLs, reachability)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review findings from axum 0.8 upgrade
- Use cookie value_trimmed() instead of value() for cookie 0.18 compat
- Update comments still referencing old :workspace_id syntax
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 61ae055ea31481f1899953e9d5f65566b8c707b1
This commit updates the EE repository reference after PR #486 was merged in windmill-ee-private.
Previous ee-repo-ref: 0059d175a6fdddf52998b183bf91059b224704ac
New ee-repo-ref: 61ae055ea31481f1899953e9d5f65566b8c707b1
Automated by sync-ee-ref workflow.
* test: add test for new get_imports endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unused import in raw_apps test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* Improve UI of script record
* Improve UI for scripts
* Remove Result & Logs loading container while flow not finised
* Improve Graph view
* Add click on a step mention
* Fix spacing when empty
* Fix step duration disappearing in recorded flows
* Modernize timeline tab
* Improve Script recording result UI
* feat: externalize recording player controls for fake-window embedding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: reorder FlowViewer tab sync effects for clarity
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: eliminate tab sync effects in FlowViewer, use selectedTab directly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove unnecessary untrack in FlowViewer tab init
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: skip tab auto-selection when selectedTab is controlled externally
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: export recording types from package
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: non-null assertion for recording.flow in FlowGraphViewer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace banned $bindable(default_value) pattern and simplify tab sync
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use svelte 5 onclick syntax on replay page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: skip db clock endpoint during replay mode
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove line numbers from script recording code display
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: hugocasa <hugo@casademont.ch>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* prepare sqlx
* Add relative imports to getDependencies of deployUI
* nit
* fix: correct get_imports doc comment, add tracing, use Set for dedup
- Fix copy-pasted doc comment on get_imports (said "get dependents")
- Add tracing::debug to get_imports handler to match get_dependents
- Use Set for O(1) duplicate detection in deploy dependency traversal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve parent_hash race condition in sync push with auto_parent
During concurrent sync push operations (parallel CLI groups or separate
CI pipelines), multiple requests could read the same remote script hash
and both try to create a new version with the same parent_hash, causing
"the lineage must be linear" errors.
Adds an opt-in `auto_parent` field to the create_script API. When set,
the backend resolves the parent_hash to the current head script at that
path within the transaction, atomically. This eliminates the client-side
race window where the parent could change between read and write.
The CLI now sends `auto_parent: true` when updating existing scripts,
so sync push is resilient to concurrent deployments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add missing auto_parent field in clone_script NewScript initializer
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: add advisory lock to serialize concurrent auto_parent script creates
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* sqlx
* fix: add sqlx anchor for CE-only user count query
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add PDF input support to AI agent with user_attachments field
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add integration tests for PDF input and backward compat
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add ContentPart::File variant for PDF support across all providers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: address review feedback on PDF support
- Extract parse_data_url_bytes and mime_to_document_format helpers in Bedrock
- Add is_document_mime helper in ai_types for centralized MIME routing
- Extract s3_object_to_content_part helper to deduplicate image_handler/openai
- Rename AnthropicImageSource to AnthropicBase64Source
- Derive Bedrock DocumentFormat from MIME type instead of hardcoding Pdf
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: merge user message and attachments into single message for Bedrock
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use resource-level scope overrides during OAuth2 token refresh
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 6db424512b0d02f86489e85f0026581b7637d6e6
This commit updates the EE repository reference after PR #484 was merged in windmill-ee-private.
Previous ee-repo-ref: c9277992608537155a9505a089aca91403d91159
New ee-repo-ref: 6db424512b0d02f86489e85f0026581b7637d6e6
Automated by sync-ee-ref workflow.
* fix: restore non-enterprise sqlx cache entries deleted by update_sqlx.sh
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update sqlx cache for latest EE changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: rename migration to avoid timestamp collision with trashbin
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: collapse duplicate match arms and simplify effective_scopes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: mask sensitive values (secrets, password args) in job logs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: replace artificial unit tests with real integration tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: consolidate into single comprehensive masking test covering 8 scenarios
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show first 3 chars of masked secrets and add security notice
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update masking notice to say "display full value"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: handle poisoned locks, deduplicate notice, mask non-string encrypted args
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* perf: snapshot-based masking, one lock per batch instead of per line
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* perf: use Aho-Corasick for O(m) single-pass matching regardless of secret count
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: track notice in snapshot (no global lock), document snapshot race trade-off
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move fs-backed cache under WINDMILL_DIR
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add WINDMILL_CACHE_PREFIX env var for per-session cache isolation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: auto-use WEBMUX_BRANCH as cache prefix for session isolation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show groups and notes in flow status viewer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: include notes/groups in flow_version_lite for run page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: GitHub Enterprise Server (GHE) support for self-managed GitHub Apps
- Fix GHE installation URL: use /github-apps/ path instead of /apps/ for non-github.com hosts
- Fix double decodeURIComponent on OAuth state param (URLSearchParams already decodes)
- Add client_id to self-managed GitHub App validation
- Bump hub scripts to GHE-compatible versions (sync, test, init, clone)
- Bump LATEST_GIT_SYNC_SCRIPT_PATH to hub/28176
- Rename "GitHub Enterprise App" → "GitHub App" in UI labels (it works for both)
- Formatting fixes in GhesAppSettings.svelte and gh_success page
EE ref: windmill-labs/windmill-ee-private@09c9ed1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update SQLx metadata
* fix: handle GHE Cloud (*.ghe.com) app installation URL path
GHE Cloud uses /apps/ like github.com, not /github-apps/ like self-hosted GHES.
Docs: https://docs.github.com/en/enterprise-cloud@latest/apps/using-github-apps/installing-a-github-app-from-a-third-party
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle GHE Cloud (*.ghe.com) installation URL and update ee-repo-ref
GHE Cloud uses /apps/ like github.com, not /github-apps/ like self-hosted GHES.
Docs: https://docs.github.com/en/enterprise-cloud@latest/apps/using-github-apps/installing-a-github-app-from-a-third-party
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update hubPaths to deprecate 28176 and use 28180 as latest sync script
Aligns with main's LATEST_GIT_SYNC_SCRIPT_PATH bump in PR #8532.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to 6bb0ff0 (includes GHE fixes)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add instance-level setting to enforce workspace prefix for HTTP routes
Add `http_route_workspaced_route` instance setting that forces all HTTP routes
to use workspace prefix (`/api/r/{workspace_id}/{route}`), mirroring the existing
`app_workspaced_route` setting for apps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: bump http trigger version on setting change to invalidate route cache
The route cache is version-based, not TTL-based. Without bumping the
version sequence when the instance setting changes, cached routes would
continue serving with the old prefix behavior until a route is
created/updated/deleted or the server restarts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: immediately refresh HTTP routers on setting change
The route cache polls every 60 seconds, but bumping the version sequence
only makes the next poll pick up changes. Explicitly call refresh_routers
after the setting reload so routes are rebuilt immediately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: replace host docker socket with dind sidecar for isolation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: comment out dind sidecar by default to avoid wasting resources
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: enable dind by default, comment out insecure host socket mount
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] feat: handle active:false in SCIM user PATCH/PUT for deprovisioning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref for SCIM active:false deprovision fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* nit sqlx
* [ee] feat: add password.disabled column for SCIM user deactivation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] feat: enforce password.disabled in auth checks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] refactor: use scim_deactivated_user table instead of password.disabled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] fix: apply SCIM filters to deactivated users, add name column
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add down migration for scim_deactivated_user
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: rename migration to avoid timestamp conflict, update sqlx cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] refactor: use password.disabled for SCIM deactivation, block login for disabled users
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] feat: show disabled toggle in superadmin user list, add disabled field to API
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add confirmation modal when disabling instance user
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve disable user confirmation text
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: revert toggle state when disable confirmation is cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: properly revert toggle on disable cancel using reset key
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move disable/enable to dropdown menu, add disabled badge on email
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: rename 'Show active users only' to 'Recently active only' to avoid confusion with disabled state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove accidentally committed gen files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use .catch() for enable user error handling in dropdown action
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: delete tokens on user removal, improve confirmation modal texts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update sqlx cache for non-enterprise code paths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore sqlx cache files deleted by incorrect prepare run
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add missing sqlx cache for non-enterprise git sync query
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to a1274aa11a83f608eacc32c0d449ca3527d98c15
This commit updates the EE repository reference after PR #473 was merged in windmill-ee-private.
Previous ee-repo-ref: 30f8c53b101b9e25107e793cdc038b0e07061739
New ee-repo-ref: a1274aa11a83f608eacc32c0d449ca3527d98c15
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add runner groups for shared-process multi-script dedicated workers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: unify dedicated worker and runner group wrappers into single multi-script wrapper
Replace per-language single-script wrappers with the unified load/exec/exec_preprocess/end
protocol. Each start_worker() now writes scripts to scripts/<safe_name>/ and uses
generate_multi_script_wrapper(). handle_dedicated_process() sends load: on start and
exec: per job instead of raw JSON args.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: merge runner groups into dedicated workers with inline arg metadata
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to match EE branch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: gate EE-only functions behind cfg(feature = "private") to fix OSS dead_code errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: auto-detect runner groups from workspace dependency annotations
- New endpoint GET /scripts/list_dedicated_with_deps: returns dedicated
scripts with parsed workspace dependency names from content annotations
- Frontend: show dep badges in DedicatedWorkersSelector with links to
workspace settings, warn when referenced dep doesn't exist, group
scripts sharing deps into "Shared runner" sections
- Remove manual "Runner groups" tab and RunnerGroupSelector component
- Remove runner_groups from WorkerConfigOpt/WorkerConfig (auto-detected)
- Fix Node.js single dedicated workers: transpile main.ts -> main.js via
Bun.build so the multi-script wrapper's dynamic import() works under Node
- Add package.json with type:module in scripts dir to silence Node warning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: unify dedicated worker wrappers with baked-in codegen and routing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add e2e tests for multi-script dedicated worker routing (bun, deno, python)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove dead generate_dedicated_worker_wrapper function
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add dependency installation to runner groups + make dep functions pub(crate)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent bun loader from intercepting absolute paths within cwd
When a plugin's onResolve returns an absolute path, Bun re-invokes
the resolver with that path. The loader was then routing it through
the remote URL resolver, breaking runner group script imports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use _wm_ prefix for runner group scripts to avoid bun loader interception
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract DENO_UNSTABLE_ARGS constant to avoid repeating flags
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: gate private-only exports behind cfg(feature = "private") for OSS build
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move format strings before handle_dedicated_process to fix lifetime
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate sqlx offline cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix sqlx
* fix: skip empty lines in deno e2e tests (double newline from console.log + '\n')
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use dict() instead of {{}} in python wrapper to avoid set literal
{{{{}}}} in format!() produces {{}} which Python interprets as an
empty set, not a dict. Use dict() which is unambiguous.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove deno from runner groups and associated tests
Deno resolves dependencies at runtime via URLs/import maps, so there's
no shared node_modules/pip install to benefit from runner groups.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: revert deno wrapper to inline old-style with exec: protocol
Since deno doesn't support runner groups, the unified multi-script
wrapper is unnecessary. Reverted to the old inline wrapper from main
but adapted to use the exec:<path>:<args> protocol.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract deno wrapper into reusable function and add e2e tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use codebase presence (not nodejs annotation) to determine wrapper import extension
On main, codebase scripts import ./main.js (pre-bundled JS).
The wrapper_ext was incorrectly based on annotation.nodejs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: improve dedicated workers UI - combine lists, better badges, tooltips
- Merge shared runners section with selected tags into one unified list
- Move language tag to right side of selector for alignment
- Change dep badge color from dark-gray to indigo
- Add tooltip on yellow warning badge explaining missing workspace dep
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: group shared runners visually in dedicated workers list
- Runner groups shown with a header (Shared runner · language · dep badge)
- Scripts in the same group nested under the header
- Standalone scripts/flows shown after groups
- Used Svelte snippet for reusable tag row rendering
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve visual separation between shared runner groups and standalone items
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: give standalone runners same header style as shared runners
- Each standalone script/flow gets its own header row with bg-surface-secondary
- Header shows "Dedicated runner" / "Flow runner" label, dep link, language badge
- Shared runner header: swapped language and dep badge positions
- Dep shown as inline link instead of badge in headers for cleaner look
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: inline standalone runner path in header, language badge on right edge, no max height
- Standalone items: path shown directly in header row (no sub-row)
- Language badge placed after flex-1 spacer (right-aligned)
- Removed max-h-64 overflow constraint from the list
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: consistent badges across runner list - dep+language on right, depBadge snippet
- Shared runner scripts: show (workspace) and language badge on right
- Standalone items: dep badges and language badge on right (after flex-1)
- Shared runner header: dep badge and language badge on right
- Extract depBadge snippet to deduplicate dep badge rendering
- Picker selector also uses depBadge snippet
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show language badge on standalone items, hide from shared runner sub-items
- Fetch script language from API when not available from workspace deps
- Hide dep+language badges from tagRow when script is inside a runner group
(already shown in the group header)
- Standalone items now always show language badge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: differentiate badge colors - gray for language, indigo for workspace deps
Matches codebase convention: gray for metadata (like script hashes),
indigo for linkable features/entities.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use transparent (bordered) badge for language - visible on all backgrounds
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use gray badge for language everywhere
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: revert skills.ts and AI files, add _wm_ exclusion to Windows loader
- Revert cli/src/guidance/skills.ts to main (not our change)
- Revert AI provider formatting changes (not our change)
- Add _wm_ prefix exclusion to loader.bun.windows.js filterResolve
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update ee-repo-ref and regenerate system prompts after merge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* perf: use DISTINCT ON in list_dedicated_with_deps to dedup at DB level
Avoids fetching all script versions and deduplicating in Rust.
Addresses PR review feedback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use sqlx query! macro for list_dedicated_with_deps and regenerate cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: dedicated worker review fixes and test coverage
- Fix Python relative imports in dedicated workers (write loader.py, add
import loader to wrapper when needed)
- Move Python colon parsing inside try/except to prevent crashes on
malformed stdin
- Add indexOf guard in Bun/Deno wrappers for malformed protocol messages
- Add stderr logging for unrecognized stdin commands in all wrappers
- Remove asyncio handling from Python wrapper (consistent with normal path)
- Add exec_preprocess protocol tests for Bun, Deno, and Python
- Add argument transformation tests (dates, bytes, kwargs, sentinel)
- Add relative import detection test for Python wrapper
- Add PreprocessedArgs variant to DedicatedWorkerResult test helper
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove symlink from git and gate has_relative_imports behind private feature
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update ee-repo-ref for dedicated_worker_ee.rs changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add mixed exec+preprocess test to use ProtocolCmd::Exec variant
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove hanging deno missing-preprocessor test
The Deno wrapper only generates the exec_preprocess handler when the
script has a preprocessor function. Without one, the message is
unrecognized and the test hangs reading stdout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 182943e5ad9bf2a905ccdf07d4e346437fb329a9
This commit updates the EE repository reference after PR #466 was merged in windmill-ee-private.
Previous ee-repo-ref: 995f701fe3754be6260fc6b679e5de8fc636e68a
New ee-repo-ref: 182943e5ad9bf2a905ccdf07d4e346437fb329a9
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
The app mode AI chat system prompt had hand-written datatable API docs
that were missing methods (fetchOneScalar, execute, query). This adds
datatable-specific extraction to generate.py so the prompt stays in
sync with the actual TypeScript and Python client APIs.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the require_admin check from blocking the entire tarball export
to only guarding the include_key=true path. Non-admins can still
export tarballs for workspace sync/git, but only admins can export
the raw workspace encryption key.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show sync resource types button when resource type is missing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show prominent error message when resource type is not found
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use sync_cached_resource_types endpoint instead of hub_sync script
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: fallback to fetching resource types from hub when cache file missing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: unify eval pipeline with production chat code path
Extract a shared headless runChatLoop() that both AIChatManager
(production) and the eval runner use, with injectable SDK clients.
Drop OpenRouter — evals now use direct provider APIs (OpenAI SDK,
Anthropic SDK) with streaming, matching production behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: re-read tools/helpers/systemMessage/model on each loop iteration
The old chatRequest() re-read this.tools, this.helpers, this.systemMessage,
and getCurrentModel() on every iteration. This matters because changeModeTool
(Navigator → Script/Flow) reassigns all of these mid-loop. Use JS getters
in the config object so runChatLoop picks up changes each iteration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: move basic git sync from EE to CE with runtime user count gating
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt for git sync CE migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: keep git sync impl in private repo, revert oss to stub
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt after merge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use LICENSE_KEY check instead of get_license_plan for runtime gating
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve git sync CE UX — use "Community Edition" wording, mention user limit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use "workspace members" instead of "users" in git sync messaging
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: lower CE git sync limit from 3 to 2 workspace members
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: simplify git sync CE alerts to warn about EE feature with member limit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add EE feature restrictions detail to CE git sync warning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show git sync settings even when >2 members, with disabled warning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show error alert when git sync settings exist but members exceed CE limit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: mention CE git sync limit is for testing and hobbyist use
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 79eeacccc0438010d7dfa60207a5cbdaf2eda08d
This commit updates the EE repository reference after PR #476 was merged in windmill-ee-private.
Previous ee-repo-ref: c4d69c6e700c16d44f909d9c7b6738b07043db98
New ee-repo-ref: 79eeacccc0438010d7dfa60207a5cbdaf2eda08d
Automated by sync-ee-ref workflow.
* chore: update sqlx cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate full sqlx cache after main merge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update sqlx cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref and regenerate sqlx cache with private feature
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use LICENSE_KEY_VALID for EE check, allow delete without access check, extract helpers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: use compile-time cfg(enterprise) gating instead of runtime license checks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 6171a91da38d6d16a88aeb1a3a4f4df78f995383
This commit updates the EE repository reference after PR #481 was merged in windmill-ee-private.
Previous ee-repo-ref: 52681940cda6d70f65aeeb7144288f060b4d736e
New ee-repo-ref: 6171a91da38d6d16a88aeb1a3a4f4df78f995383
Automated by sync-ee-ref workflow.
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to b5c8af4df9ba2c39fdd494d7a40f9a92fbff8abc
This commit updates the EE repository reference after PR #482 was merged in windmill-ee-private.
Previous ee-repo-ref: 6e5b2741831468a7b30b26c0df1241e6141c6833
New ee-repo-ref: b5c8af4df9ba2c39fdd494d7a40f9a92fbff8abc
Automated by sync-ee-ref workflow.
* fix: gate CE_GIT_SYNC_MAX_USERS behind cfg(not(enterprise))
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* [ee] fix: serve index disk storage sizes from /srch/ endpoint
On multi-container deployments, the API server doesn't have the index
files on its local disk, so disk size was always reported as 0.0B.
Added a new GET /srch/index/storage/disk endpoint that calculates disk
sizes on the indexer process (which owns the files). The frontend now
fetches disk sizes from this endpoint in parallel with the status call.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 71aab648925f31cde37efd31d79a7f3a977fd42a
This commit updates the EE repository reference after PR #480 was merged in windmill-ee-private.
Previous ee-repo-ref: b3e0000e2528809302c18f36930aebf3d004747a
New ee-repo-ref: 71aab648925f31cde37efd31d79a7f3a977fd42a
Automated by sync-ee-ref workflow.
* chore: update ee-repo-ref to indexer-disk-storage-zero branch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update sqlx metadata and ee-repo-ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* [ee] chore: update tantivy from 0.24 to 0.26
- Rebase windmill-labs/tantivy fork onto upstream 0.26
- Bump serde pin from 1.0.219 to 1.0.220 (required by tantivy 0.26's time dependency)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to ec613f2db9e72e32e9131181546dcd679405a782
This commit updates the EE repository reference after PR #479 was merged in windmill-ee-private.
Previous ee-repo-ref: 920cf601b0651b7ba94493668ea051e00f3e74bf
New ee-repo-ref: ec613f2db9e72e32e9131181546dcd679405a782
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: use /apps_raw/get/ redirect URL for raw apps set as workspace default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update sqlx cache for default_app query
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add selfApproval option to WAC waitForApproval + inline approval buttons
Add self-approval configuration to WAC workflows and inline
approve/reject buttons in WorkflowTimeline.
- TS SDK: add selfApproval option to waitForApproval()
- Python SDK: add self_approval param to wait_for_approval()
- Backend: store approval_conditions in flow_status for WAC,
enforce self-approval checks on resume endpoints
- Frontend: show Approve/Reject buttons in timeline with form
support (EE), gated by user permissions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: revert sqlx query change + regenerate system prompts
- Revert get_suspended_flow_info to use original sqlx::query_as!
with COALESCE to avoid sqlx offline cache mismatch in CI
- Detect WAC by checking if FlowStatus parsing fails + suspend > 0
- Re-fetch flow_status column separately for WAC approval conditions
- Regenerate auto-generated system prompt files for SDK changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: use resume URLs for WAC inline approval buttons
- Backend generates HMAC-signed resume/cancel URLs when creating
WAC approval, stores them in timeline entry and approval meta
- Frontend uses anonymous resume endpoint (like classic flows)
with fallback to resumeSuspendedFlowAsOwner for admins
- Buttons show for everyone when URLs are present; server-side
self_approval_disabled check enforces restrictions
- Show warning for admins/owners when self-approval is disabled
- selfApproval: false requires EE (errors at dispatch on CE)
- self_approval_disabled check moved outside user_auth_required
gate so it works independently
- WAC detection no longer requires task import
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add resume_suspended and approval_info endpoints
- New approval_token DB table for token-based approval access
- New POST /jobs_u/flow/resume_suspended/{job_id} endpoint:
- OptAuthed: works with login or approval_token
- Checks approval_conditions (self_approval, groups, auth)
- Admins/owners bypass rules
- New GET /jobs_u/flow/approval_info/{job_id} endpoint:
- Returns form, rules, can_approve status
- HMAC anonymous endpoint now bypasses all approval_conditions
(secret = full capability)
- getResumeUrls approvalPage URL now uses token format
- WAC approval dispatch generates and stores approval tokens
- Mark resumeSuspendedFlowAsOwner as legacy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: simplify frontend to use resume_suspended endpoint
- OpenAPI spec updated with resume_suspended and approval_info endpoints
- WorkflowTimeline: removed URL parsing, now calls single
resumeSuspended endpoint for both approve and reject
- Buttons show for any logged-in user viewing the job (backend
enforces authorization rules)
- Kept self-approval warning for admins
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: stateless approval tokens, new approval page, FlowStatusWaitingForEvents update
- Replace DB-stored approval tokens with stateless HMAC derivation:
token = HMAC(workspace_key, job_id + "approval_token")
Verifiable without DB lookup, not reversible to resume secret
- Drop approval_token migration (no DB table needed)
- FlowStatusWaitingForEvents: use resumeSuspended endpoint instead
of URL parsing + resumeSuspendedFlowAsOwner
- New approval page route /approve/{ws}/{job}?token= that uses
approval_info and resume_suspended endpoints
- Old approval page route kept for back-compat
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: match old approval page content in new approval page
- Add FlowMetadata, JobArgs, FlowGraphV2, DisplayResult
- Add approvers with tooltips, flow arguments section
- Add admin self-approval bypass warning
- Add "Open run details" link
- Fetch full job alongside approval_info for all UI data
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: filter _MODULES from args, show 'workflow' for WAC approvals
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove deno template from approval/prompt SuspendDrawer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: approval page form display + hide deno from approval script picker
- Fix form schema rendering on new approval page by wrapping flat
WAC form schemas in { properties, order } for SchemaForm
- Hide deno from the approval step language picker in flow editor
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove deno from canHaveApproval in script_helpers.ts
The insert menu uses canHaveApproval() from script_helpers.ts via
FlowInputsQuick, not the displayLang function in FlowInputs.svelte.
Revert the unnecessary FlowInputs.svelte change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: return form schema and description in approval_info for classic flows
The approval_info endpoint was returning None for form_schema on
classic flows. Now fetches raw_flow to get suspend.resume_form
schema, hide_cancel, and the step's completed result for description.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: inline Login component on approval page instead of redirect
Show the Login component directly on the approval page when
authentication is required. On successful login, reloads user
and approval info without navigating away.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show resume buttons for all users, not just owners
The resume_suspended endpoint handles authorization server-side,
so the frontend should always show the buttons. Remove isOwner
gate and the "cannot resume" message.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent layout shift on resume by removing spinner from cancel button
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent resume button expansion by using disabled instead of loading
The loading prop adds a Loader2 spinner that expands the button width.
Use disabled={loading} instead to prevent layout shift.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: approval page login redirects back with full page reload
Set rd to the full URL (starts with http) so Login.redirectUser()
uses window.location.href instead of goto(), triggering a full page
reload after login. This ensures the approval page re-fetches data
as an authenticated user.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: fetch flow definition from flow_version when raw_flow is null
Deployed flows don't store raw_flow on the job. Fall back to
flow_version table using runnable_id to get suspend settings
(form schema, hide_cancel) for the approval_info endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show specific reasons when user cannot approve
Display whether denial is due to self-approval being disabled,
required group membership, or both.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: support both nested and flat form schema in waitForApproval
Users can now pass either:
waitForApproval({ form: { schema: { name: { type: "string" } } } })
or:
waitForApproval({ form: { name: { type: "string" } } })
Both WorkflowTimeline and approval page handle both formats.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: convert sqlx query macros to non-macro for CI offline cache
Replace sqlx::query! and sqlx::query_scalar! with sqlx::query and
sqlx::query_as to avoid SQLX_OFFLINE cache misses in CI.
Also remove unused LogIn import from approval page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: suppress dead code warning + unused isOwner variable
- Add #[allow(dead_code)] to without_flow method (CI -D warnings)
- Rename isOwner to _isOwner in FlowStatusWaitingForEvents (unused)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: security and robustness fixes from PR review
- Add workspace_id verification in resume_suspended to prevent
cross-workspace approval (#3)
- Fix token leakage: use relative path for login redirect instead
of full URL with token (#4)
- Handle getJob failure independently from approval_info so the
page works for unauthenticated users (#7)
- Clear error state on successful data load (#13)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback — shared token gen, rand resume_id, UX
- Move generate_approval_token to windmill-common::variables (shared
between windmill-api and windmill-worker, eliminates duplicate HMAC)
- Use rand::random::<u32>() for resume_id instead of DefaultHasher
- Stop polling after approve/reject on approval page
- Add cancelLoading state to WorkflowTimeline Reject button
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add instance-level AI settings with workspace fallback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add AI step to onboarding setup wizard
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: thread workspace prop through resource editor and disable chat offset
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "fix: thread workspace prop through resource editor and disable chat offset"
This reverts commit 9fea9cc0c239f6432d1fef1487c45e74ab752e21.
* fix: set workspace store and disable chat offset during AI setup step
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: thread workspace and disableChatOffset props through resource editors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: populate workspace and user stores for AI step path component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: initialize AI clients for test key during onboarding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract AI config state into InstanceAISettings component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move AI config state ownership into AISettings component
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Persist instance AI settings before navigation
* Reload effective workspace AI state after save
* Scope AI key tests to the rendered workspace
* Add post-create AI onboarding for new workspaces
* Unify instance AI settings header
* Fix instance AI drawer offset on workspace selection
* Add instance AI fallback settings behavior
* Update sqlx metadata
* Update sqlx metadata
* Clarify active instance AI in workspace settings
* Refresh workspace AI state after instance AI save
* Declare instance AI summary in API schema
* Normalize empty instance AI config handling
* Clean up workspace AI settings UI
* Unify AI config provider checks
* Split AI settings metadata from effective config
* Propagate instance AI cache invalidation across servers
* Fix AI settings dirty state tracking
* Update sqlx metadata
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use bind parameters for folder owner in jsonb_set queries
Replace format! string interpolation of owner into jsonb_set path
with proper $N bind parameters to prevent potential SQL injection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to faeaa43bbe2ba4804f80b828b85fd4d6daef096c
This commit updates the EE repository reference after PR #477 was merged in windmill-ee-private.
Previous ee-repo-ref: 0d4444cb5825fa43629d856cc8565cc052512d4c
New ee-repo-ref: faeaa43bbe2ba4804f80b828b85fd4d6daef096c
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add flow group nodes core infrastructure
Add group data model (start_id/end_id boundary pairs), GroupEditor for
CRUD operations, groupDetectionUtils for membership computation and
validation, GroupedModulesProxy for reactive sync, and compound layout
support. Update openflow.openapi.yaml with group schema.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add group UI components and rendering
Add GroupOverlay with bounding box and z-ordering, GroupHeader with
StepCountTab and ellipsis menu, GroupNodeCard, GroupNoteArea for inline
markdown notes, CollapsedGroupNode/CollapsedSubflowNode for collapsed
rendering, GroupEndNode/GroupHeadNode boundary markers, and group
actions in NodeContextMenu and SelectionBoundingBox.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: integrate groups into flow graph, builder, and existing components
Wire group support into FlowGraphV2 (overlays, collapsed rendering,
group-aware layout), graphBuilder (GroupedModule tree, container
collapse/expand, group boundary nodes), BaseEdge (drop targets for
group operations), ModuleNode (collapsed container rendering), and
flow map components (schema item grouping). Remove SubflowBound in
favor of CollapsedSubflowNode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove banned $bindable(default) pattern and dead ternary
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: decouple collapse state from grouped module tree
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: pass groups prop to FlowGraphV2 and use GroupDisplayState via graphContext
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove group membership system, compute nesting depth from visual bounds
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: simplify GroupOverlay bounds, remove unused headerY and showNotes prop
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: populate innerNodeIds for expanded subflow overlay
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove expanded subflow overlay feature for separate PR
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: flatten groups in getContainerModules to prevent crash on collapsed containers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add drag-to-move support for group nodes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: derive group boundaries from expanded membership to prevent splitting existing groups
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: catch group validation errors and display as flow graph alert
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add unit tests for group validation in buildGroupedModules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reject virtual nodes (Input, Result, Trigger) from groups
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add virtual node rejection tests for buildGroupedModules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: exclude preprocessor and failure module from groups
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: disable Create group button when preprocessor is selected
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reject selection entirely when it contains excluded nodes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove unnecessary excludeIds from buildGroupedModules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove debug console.log from FlowGraphV2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use cross-browser CSS grid trick for group summary input auto-sizing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hide group boundary edges and reformat GroupNoteArea
Hide edges between group header and first node, and between last node
and group-end, keeping them in the DOM but visually hidden.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: stop FlowGraphV2 from reading groups via groupEditorContext
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show module previews with status, selection, and suspend popover in collapsed groups
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract collapsible implicit containers to separate branch
Remove collapse/expand functionality for implicit containers (forloops,
while loops, branches) from this branch. Backed up as
collapsible-implicit-containers-backup for later rebase.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: use original reactive modules for graph node data instead of proxy snapshots
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent node loss when moving into forloop inside a group
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: replace GroupedModule proxy with structure-only FlowStructureNode tree
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use "group-" prefix for group IDs instead of "note-"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update group boundaries when renaming a module ID
When a module at a group boundary (start_id or end_id) is renamed,
the group definitions now get updated before the reactive rebuild,
preventing stale references that would break the flow structure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update graph layout when removing a group note
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add opaque background behind test run button to prevent see-through
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: detect and reject duplicate group IDs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: simplify group creation validation with early marker normalization
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use $state.raw in MiniFlowGraph to avoid xyflow performance warning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: address code review feedback
- Revert backend traverse_modules change (not part of this feature)
- Use Map for node lookup in GroupOverlay (O(1) vs O(n) per group)
- Extract computeNodeExtraSpace to nodeExtraSpace.ts for testability
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: address PR review feedback
- Compute group depths from structure tree O(n) instead of O(n²) bounds comparison
- Remove unnecessary $derived(groups) in GroupOverlay
- Remove unused collapsed field from container types in OpenAPI spec
- Use NODE.width constant in GroupNodeCard instead of hardcoded 275px
- Add comment explaining intentional stale preservation in rebuild()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve flow groups during dependency job re-serialization
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve Svelte state_referenced_locally warnings in GroupHeader and FlowGraphV2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show subflow groups when expanding a subflow in the graph
- Store both modules and groups when expanding a subflow
- Pass groups to buildStructureTree so group nodes render
- Include subflow groups in overlay rendering and collapse tracking
- Clone modules for prefix rewriting to avoid state_unsafe_mutation
- Register expanded subflow modules in moduleMap before prefix rewriting
- Disable group editing in expanded subflows and read-only views
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore accidentally removed code from main
- Restore subflowBound selection handling in selectionUtils
- Restore comments in SelectionBoundingBox
- Restore deletable={false} in FirstStepInputs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove redundant adjacency check from MoveManager
The disableMoveIds check already prevents all invalid drop targets,
making the adjacencySourceId/adjacencyTargetId fields unnecessary.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate auto-generated files after OpenAPI schema change
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate cli skills after main merge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: include groups in view_graph localStorage state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: centralize canCreateGroup and replace group note with group creation
- Add canCreateGroup StateStore to GroupEditorContext, computed in FlowGraphV2
- Replace "Create group note" with "Create group" in FlowSelectionPanel
- Remove "Add note" from selection bounding box dropdown
- Remove unused NodeContextMenu component
- Wire createGroup through FlowModuleSchemaMap → FlowGraphV2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reject groups spanning parallel branches and surface ill-formed group errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate auto-generated files after main merge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: ensure modules appears before groups in YAML export
Svelte 5's $state proxy registers groups as a tracked property before
it's explicitly set, causing it to appear before modules in Object.keys
iteration. Reorder the value object at export time for readable YAML.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: address second round of PR review feedback
- Add comment explaining duplicateMultiple bypasses structure tree
- Add warning log for inverted ranges in computeGroupModuleIds
- Use NODE.width constant in CollapsedGroupNode instead of hardcoded 275px
- Simplify redundant condition in getGroupsEmptiedBy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove stored group ID, derive ephemeral key from start_id:end_id
Groups no longer store an `id` field. Instead, a `groupKey(g)` helper
derives an ephemeral key from `${start_id}:${end_id}` at read time.
This simplifies the schema while preserving all runtime functionality.
When boundaries shift (module deletion), runtime state (collapse,
note heights) is remapped to the new key via GroupDisplayState.remapGroupKey.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add note button, save/cancel hints, and rename collapsed_by_default to autocollapse
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: propagate selection from collapsed group badges to external listeners
Pass eventHandlers to GroupModuleIcons so clicking a module badge
calls both selectionManager.selectId (visual highlight) and
eventHandlers.select (side panel propagation via onSelect).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate auto-generated files after main merge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hide In/Out popovers and actions during click-to-move
Replace isDragging with isMoving derived that covers both drag-move
and click-move states, disabling popovers, delete button, and test
run button during any move operation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Git uses libcurl with GnuTLS on Debian, which doesn't read
SSL_CERT_FILE or CURL_CA_BUNDLE for CA trust. When the OTEL tracing
proxy is enabled, git clone fails with "certificate signer not trusted"
because it can't verify the proxy's MITM certificate.
Adding GIT_SSL_CAINFO pointing to the proxy CA cert fixes this.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add summary field for native triggers (nextcloud, google)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add nullable to NativeTriggerData summary in openapi spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: include summary in native trigger search index
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: surface permissioned_as selector in trigger editor UI
Add OnBehalfOfSelector to TriggerEditorToolbar so users can see and
control who a trigger runs as. Admins/deployers can preserve the
current permissioned_as or pick a custom user; non-admins see the
current value but options are disabled.
Applies to all trigger types: schedule, kafka, http, websocket,
postgres, nats, mqtt, sqs, gcp, and email.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move permissioned_as selector from toolbar to config area
Move OnBehalfOfSelector out of TriggerEditorToolbar (too cluttered)
into a new PermissionedAsLine component rendered at the top of each
trigger editor's config body. Lighter footprint, same functionality.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show explicit warning when saving will change permissioned_as
Use an Alert (warning/info) to clearly show who the trigger currently
runs as and whether saving will change it. Non-admin users see a
warning that it will switch to them. Admins see the OnBehalfOfSelector
to preserve or pick a custom user.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make permissioned_as line subtle instead of big alert box
Replace the Alert component with a small inline text line using
text-2xs. Shows warning arrow + yellow text only when saving will
actually change the permissioned_as.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: split permissioned_as display for admin vs non-admin
Admins see just "Permissioned as" label + the OnBehalfOfSelector
(no duplicate username). Non-admins see the plain text line with
warning arrow when it will change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show warning for admins too when permissioned_as will change
Admins now see a yellow warning next to the selector when their
choice differs from the current permissioned_as value.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use consistent warning text for permissioned_as change
Both admin and non-admin warnings now say
"will change to <user> on save" instead of using an arrow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: bold permission strings in permissioned_as warnings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: bold the non-editable permissioned_as value too
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove mono font from non-editable permissioned_as value
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add consistent bottom margin to permissioned_as line
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: consistent spacing for permissioned_as line
Move PermissionedAsLine outside the gap-8 div in schedule editor
and increase margin to mb-4 for consistent spacing across all
trigger types.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: clean up stale dependency map entries for renamed scripts
When a script is renamed, trigger_dependents_to_recompute_dependencies()
could find the archived script at the old path and create a dependency
job for it. This job would process the old code and recreate stale
dependency_map entries, causing incorrect deployment warnings.
Add `AND archived = false` to the script lookup query so that renamed
(archived) scripts at old paths trigger clear_map_for_item() cleanup
instead of spawning dependency jobs for obsolete code.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: also filter archived flows in trigger_dependents
Apply the same archived check to the flow lookup query. The flow table
has an archived column, so when a flow is renamed/archived its
flow_version rows would still be found. Join against the flow table
and filter archived = false to trigger cleanup instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* revert: remove unnecessary flow archived check
Flow renames delete the old flow row and INSERT a new one at the new
path (for FK constraints on flow_version). There is no archived flow
row left behind, so the original query is already correct for flows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace unsafe string interpolation (format!("'{}'", t)) with
sql_builder::quote() in SQL query construction. The tags parameter in
count_completed_jobs_detail was directly interpolated without escaping,
allowing authenticated users to inject arbitrary SQL via the query string.
Also hardens LIKE clauses, JSON operators, and JOIN conditions across
query.rs and variables.rs that used manual .replace("'", "''") instead
of the crate's quote() function, and converts format-interpolated bind
values to parameterized queries where possible.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Show effective isolation level on workers page, not configured
The workers page displayed the configured isolation level (nsjail/unshare)
even when the binary wasn't actually available, which was misleading.
Now shows "none (nsjail unavailable)" or "none (unshare unavailable)"
when the setting is enabled but the binary failed its startup test,
so admins can immediately see the mismatch from the UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Simplify: use standard 'none' value when isolation binary unavailable
Keep the string as one of the 3 known values (nsjail/unshare/none)
since the frontend checks === 'none' for the warning badge. Now if
nsjail/unshare is configured but the binary is unavailable, it
correctly reports 'none' so the warning badge shows up.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Debounce node works
* sqlx prepare
* sqlx prepare
* fix: address PR review issues for flow node debouncing
- Add sibling check in parent-walking loop to avoid killing branchall siblings
- Remove stale .sqlx cache files from earlier iterations
- Remove single-variant FlowNodeDebounceResult enum, use Result<()>
- Parse flow value once in version guard, recurse into nested modules
- Fix Svelte reactivity when switching selected flow modules
- Fix Tab indentation in FlowModuleComponent
- Use integer types in OpenAPI spec for debounce fields
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ee repo ref
* nit sqlx
* add Debouncing: None
* ee repo ref
* ee repo
* sqlx update
* fix: reject node-level debouncing inside branches (branchall/branchone)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert "fix: reject node-level debouncing inside branches (branchall/branchone)"
This reverts commit fa4820dde2.
* ee repo
* sqlx prepare
* sqlx prepare
* feat: add MIN_VERSION_SUPPORTS_NODE_DEBOUNCING (1.658.0) version guard
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: mark node-level debouncing as EE only in openflow schema
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: guard node debouncing against parallel steps (len > 1)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* generate system prompts
* system prompts
* chore: update ee-repo-ref to c04f3851c03758662e4936ff4b6e71bc56dbae7e
This commit updates the EE repository reference after PR #451 was merged in windmill-ee-private.
Previous ee-repo-ref: d140bb8944dfe3efb23cf8c12f556eacf30e2f87
New ee-repo-ref: c04f3851c03758662e4936ff4b6e71bc56dbae7e
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* SQL Query builders in Rust
* Remove frontend sql scripts and substitute at execution
* fix null value bug
* Handle WM_INTERNAL_DB marker for apps deployed prior
* Revert policy handling
* Fix database studio empty string as where clause
* check policy
* Revert "check policy"
This reverts commit 3ea7899979.
* Revert "Fix database studio empty string as where clause"
This reverts commit 432fc87915.
* Revert
* legacy comments
* Move DDL queries to backend
* tests
* move bigquery bun scripts to backend
* expand markers + other nits
* fix: escape sql literals in query builders and async preview sql
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: quote all user-supplied identifiers in query builders to prevent SQL injection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: suppress dead_code warnings for deserialization-only fields and test-only helpers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct DDL test assertions and drop_table schema handling for non-schema DBs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* MySQL fix
* Fix 0/1 bool
* MySQL fix Yes/No casing
* Better error toasts
* Fix ms sql ntext cast
* fix: quote table name in Snowflake SHOW PRIMARY KEYS query
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: quote schema and table in Snowflake SHOW IMPORTED KEYS query
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: quote BigQuery dataset name in metadata query
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: remove invalid + separator in MSSQL CONCAT for count query
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add typed request body schema to OpenAPI spec for runnables without preprocessor
For HTTP routes and webhooks whose runnables (scripts/flows) don't have a
preprocessor, generate a typed request body in the OpenAPI spec using the
runnable's argument schema. Routes with preprocessors or wrap_body keep
the existing generic default request body.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix sqlx
* fix: add warning logs for schema fetch failures and strip non-OpenAPI keys
- Log tracing::warn when DB queries for schema fail instead of silently
swallowing errors with .ok()
- Strip $schema and order keys from the JSON Schema before embedding in
the OpenAPI spec for broader client compatibility
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add tracing dependency to windmill-api-openapi
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix worker panic when job_isolation changed to unshare at runtime
When an admin changes the Instance Setting "job_isolation" to "unshare"
while UNSHARE_PATH was never initialized (binary not available at startup),
the worker panics in build_command_with_isolation().
This happens because reload_job_isolation_setting() in monitor.rs validates
nsjail availability but not unshare availability before applying the setting.
Fix:
- Add unshare availability check in reload_job_isolation_setting(), matching
the existing nsjail check
- Replace panic! in build_command_with_isolation() with an error log and
graceful fallback to running without isolation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Improve error logging for unshare/nsjail unavailability at startup
- Upgrade unshare init logs from warn/debug to error level with detailed
diagnostics (exit code, stderr, common causes, impact on job isolation)
- Upgrade nsjail init logs from info/warn to error level with clear
messaging about unavailability consequences
- Force both UNSHARE_PATH and NSJAIL_AVAILABLE initialization at worker
startup (not just when isolation is currently enabled) so availability
is always logged regardless of current config
- Add explicit startup warnings when worker is configured for isolation
but the binary is unavailable, referencing the init errors above
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix
Signed-off-by: pyranota <pyra@duck.com>
* reduce tests
Signed-off-by: pyranota <pyra@duck.com>
* update
Signed-off-by: pyranota <pyra@duck.com>
* fix
Signed-off-by: pyranota <pyra@duck.com>
* update
Signed-off-by: pyranota <pyra@duck.com>
* WIP: stash changes after merge with origin/main
* Delete backend/parsers/windmill-parser-wasm/Cargo.lock
* reset cargo.toml
* feat(cli): integrate dependency tree into generate-metadata command
- Add isDirectlyStale field to DependencyNode for staleness tracking
- Update addScript to accept itemType, folder, isRawApp, isDirectlyStale
- Update propagateStaleness to use isDirectlyStale field instead of parameter
- Handlers now determine staleness and pass it to tree.addScript
- generate-metadata calls propagateStaleness() and populates staleItems from tree
- Pass legacyBehaviour=false and tree to handlers during generation phase
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): store originalPath in tree for correct handler invocation
Scripts need the path with extension to be passed to the handler.
Added originalPath field to DependencyNode to track this.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix parsers
Signed-off-by: pyranota <pyra@duck.com>
* rever sqlx removal
* update sqlx
* feat: make py-imports parser WASM-compatible and add as separate WASM package
Gate heavy deps (sqlx, windmill-common, async-recursion, toml, pep440_rs,
tracing) behind cfg(not(wasm32)). Make parse_code_for_imports,
parse_relative_imports, NImport, and ImportPin public. Remove duplicate
import_parser from parser-py (reset to origin/main). Add py-imports-parser
feature to windmill-parser-wasm and py-imports target to build.nu.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* safer return
* update
* fix: CLI metadata fixes - folder filter, staleness detection, WASM py-imports setup
- Fix lazy_static cfg gating for WASM compatibility (split into separate blocks)
- Fix folder argument filter to match specific file paths (not just directories)
- Fix staleness detection to use checkHash with conf (includes module hashes)
- Convert relative_imports_skip tests from Deno to bun APIs
- Add windmill-parser-wasm-py-imports to CLI and build-npm dependencies
- Relax module stale test to not require per-module change detail in output
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore temp_script_refs parameter in parse_python_imports
Re-adds the temp_script_refs parameter that was lost when resetting
py-imports crate to origin/main. This enables resolving relative imports
from not-yet-deployed scripts during CLI lock generation.
* fixes
* extend testsuit
* update ee repo ref
* fix: diff endpoint bytea cast, upload only mismatched scripts
- Add POST /scripts/raw_temp/diff endpoint to batch-compare local content
hashes against deployed versions using Postgres sha256()
- Use convert_to(content, 'UTF8') instead of content::bytea to avoid
failure on scripts containing backslash sequences (e.g. \n)
- CLI now diffs all scripts against deployed, uploads only mismatched ones
- propagateStaleness no longer deletes non-stale nodes (needed for diff)
- Suppress verbose log.info messages during metadata generation
- Add E2E tests for locally modified and unpushed helper scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* rework
* sqlx
* fixes
* add index
* expand tests
* fix flows
* archive script before executing
* disable tests for ci
* skip Python-dependent E2E tests on CI
Tests requiring the python backend feature are skipped when
CI_MINIMAL_FEATURES=true since CI builds with zip-only features.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make flow fixture lock optional and reset nonDottedPaths after tests
Flow fixtures no longer emit an empty lock file by default. The lockContent
parameter controls whether a lock: "!inline ..." line appears in flow.yaml.
This prevents flows from appearing "up-to-date" when they should be processed
by generate-metadata.
Also adds afterAll to reset setNonDottedPaths(false) so global state doesn't
leak between test files when run together.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* debug: add error logging in withTestBackend to diagnose CI failures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* debug: add --bail 1 to CI test runner to show full error on first failure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* debug: include CLI stdout/stderr in assertion message for workspace deps test
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: set WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES in test backend
The workspace deps feature requires workers to report their version, but
in test/CI there are no separate workers (standalone mode). The version
check fails because workers haven't had time to ping yet. Setting this
env var bypasses the version check.
Also reverts --bail 1 from CI workflow now that the root cause is fixed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* debug: add --bail 1 to Windows CI and assertion messages for Windows failure diagnosis
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: replace TEMP_SCRIPT_REFS_PLACEHOLDER in bun builder tests
The loader.bun.js now includes a TEMP_SCRIPT_REFS_PLACEHOLDER that must
be replaced before execution. The builder tests were missing this
replacement, causing all 6 bun_builder_tests to fail.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use cdirFwd in Windows loader filterLoad regex
Raw cdir (with backslashes) interpolated into RegExp causes \r to
become carriage return and \w to become word-char, so filterLoad
never matches main.ts. This prevents replaceRelativeImports from
running, leaving bare relative imports like "./script_b" in the
bundled output, which scanImports then misparses as package ".".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: Windows filterLoad regex + graceful fallback for old backends
- Fix filterLoad in loader.bun.windows.js to match both native backslash
and forward-slash paths from Bun's resolver by escaping cdir for regex
- Wrap uploadScripts in try/catch so generate-metadata degrades gracefully
when the backend lacks /raw_temp endpoints (locks use deployed versions)
- Add TODO for missing TEMP_SCRIPT_REFS support in Windows loader
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* debug: add loader/builder debug logging for Windows CI diagnosis
Temporary console.log statements to understand:
- What path Bun passes to onLoad for main.ts
- Whether filterLoad regex matches
- Whether replaceRelativeImports fires
- What the bundled output contains
- What imports scanImports extracts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: trigger CI for cli path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: trigger CI via workflow file change
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add TEMP_SCRIPT_REFS to Windows loader, use .ts extensions in test imports
- Add TEMP_SCRIPT_REFS_PLACEHOLDER support to loader.bun.windows.js
(mirrors loader.bun.js) so CLI lock generation can resolve imports
from locally-modified scripts on Windows
- Use .ts extensions in all test relative imports to work around the
Windows filterLoad regex bug (replaceRelativeImports doesn't fire
on Windows, so extensionless imports fail)
- Remove unused uploadSucceeded variable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove debug logging from loader_builder.bun.js
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove windmill-parser-wasm-py-imports from frontend package.json
This dependency is only needed by the CLI, not the frontend.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* debug: add temp_script_refs logging for Windows CI investigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: remove --bail 1 from Windows CLI tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: normalize backslashes in folder filter treePath lookup (Windows)
On Windows, item.path (originalPath) uses backslashes but tree keys
use forward slashes. The isRelevant filter's touchesFolder call
passed the unnormalized path to traverseTransitive, which couldn't
find the node. This caused cross-folder importers to be excluded
from generate-metadata when a folder argument was specified.
Also removes debug logging from previous commit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update cli-tests.yml
* fix: normalize backslashes in strict-folder-boundaries warning message (Windows)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to fe8f0d1d7448464c98474d994e6492c0a45e8e38
This commit updates the EE repository reference after PR #467 was merged in windmill-ee-private.
Previous ee-repo-ref: 03e6eaf950776c96b9581848a583af9ad735be60
New ee-repo-ref: fe8f0d1d7448464c98474d994e6492c0a45e8e38
Automated by sync-ee-ref workflow.
* revert cli-tests.yml
---------
Signed-off-by: pyranota <pyra@duck.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
On CE (without private feature), v2_job_debounce_batch is never
populated because maybe_debounce_post_preprocessing is EE-only.
The accumulation query returns zero rows, producing an empty array
that replaces the original nodes_to_relock value. This causes flow
modules to never get relocked when triggered by relative imports.
Fix: only replace the original value when the batch query actually
returned entries to accumulate.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add AZ_ACCOUNT_NAME_WORKSPACE_RESTRICTIONS env var
Add workspace restrictions by Azure account name, similar to the existing
S3_BUCKETS_WORKSPACE_RESTRICTIONS for bucket names. Refactored parsing
into a shared parse_restrictions_from_str function.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to a997285e976d0642b72584e1966a70a79d84e7dc
This commit updates the EE repository reference after PR #472 was merged in windmill-ee-private.
Previous ee-repo-ref: 5718dc7deca18ad52ffb413813e97b8ca75805b8
New ee-repo-ref: a997285e976d0642b72584e1966a70a79d84e7dc
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
- Add documentation about the $.delegated runtime error that occurs when
the Svelte runtime version in node_modules doesn't match the compiler
version used by wmill sync push.
- Change the push command in CLI reference to use --extra-includes for
targeted pushes instead of blanket wmill sync push.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add SSRF protection to SAML and MCP OAuth endpoints
- Add shared SSRF URL validation utility (windmill-common/ssrf.rs) that blocks private/loopback/link-local IPs and validates DNS resolution
- Move test_metadata to authed service requiring superadmin access
- Strip response body from SAML metadata parsing errors
- Add SSRF blocklist to MCP OAuth discover, start, and client registration endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt for SSRF fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 563877bf1c8b4184f638bab51be89b1c0aec6dad
This commit updates the EE repository reference after PR #471 was merged in windmill-ee-private.
Previous ee-repo-ref: a600fe1807ea267f87a57360f4b48bf917776723
New ee-repo-ref: 563877bf1c8b4184f638bab51be89b1c0aec6dad
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: stop_after_if with empty error_message no longer prevents flow from stopping
When skip_if_stopped=true and error_message="" were both set, the flow
would continue executing instead of stopping because the empty string
was converted to a default error message, which triggered the error
handler path. Now skip_if_stopped takes precedence and the two options
are treated as mutually exclusive in both backend and frontend.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts after openflow schema change
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: replace email with permissioned_as for triggers/schedules
Add a new `permissioned_as` column (format: `u/{username}`, `g/{group}`,
or raw email) to all trigger tables and schedule. This value is used
directly for job permission checks, removing the need for email lookups
when creating/updating triggers.
- Migration: add permissioned_as to all 9 trigger tables + schedule,
drop email from trigger tables (schedule keeps it for backwards compat)
- Backend: resolve_email() (async, DB) -> resolve_permissioned_as() (sync)
- Email cache: get_email_from_permissioned_as() with quick_cache for
places that still need email (fetch_api_authed, schedule backwards compat)
- Frontend: rename email/preserve_email -> permissioned_as/preserve_permissioned_as
in deploy data and OpenAPI schemas
- Tests updated for new field names and u/{username} format
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix sqlx/build
* update ee ref
* refactor: simplify resolve_edited_by to always use authed username
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix compile + migration
* update ref
* test: add trigger trait method tests for permissioned_as queries
Add tests that call TriggerCrud and Listener trait methods directly
to verify dynamic SQL correctly references the permissioned_as column.
Covers get_trigger_by_path, list_triggers, set_trigger_mode, and
fetch_enabled_unlistened_triggers for all trigger types.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* update sqlx
* fix: use permissioned_as directly for schedules and fix audit RLS for groups
- Schedule: permissioned_as only set on create, not on edit/set_enabled
- Schedule: stop reading email column, use get_email_from_permissioned_as
- Triggers: use fetch_api_authed_from_permissioned_as instead of edited_by
- Triggers: rename listener fields for clarity (username -> edited_by)
- Fix audit author username for group permissioned_as (g/test -> group-test)
to match session.user, preventing RLS policy violations on audit_partitioned
- OpenAPI: remove permissioned_as/preserve_permissioned_as from EditSchedule
- Add backwards-compat comments for schedule email writes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts for permissioned_as field
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix build
* refactor: generalize onBehalfOf naming, add permissioned_as to EditSchedule
- Frontend: rename onBehalfOfPermissionedAs -> onBehalfOf with comments
explaining it carries emails for flows/scripts and permissioned_as for
triggers/schedules
- Frontend: rename getOnBehalfOfEmail -> getOnBehalfOf,
getOnBehalfOfPermissionedAsForDeploy -> getOnBehalfOfForDeploy,
customOnBehalfOfEmails -> customOnBehalfOf
- Backend: add optional permissioned_as/preserve_permissioned_as to
EditSchedule with COALESCE (only updates when provided)
- Backend: add on_behalf_of audit log for schedule edit
- Backend: remove unused resolve_on_behalf_of_permissioned_as
- Tests: remove email assertions from schedule update test (email is
just backwards compat, only permissioned_as matters)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve email column when permissioned_as is preserved on schedule edit
Derive email from the preserved permissioned_as via cache lookup instead
of always writing authed.email. This keeps the email column consistent
with the old behavior for backwards compat with old workers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update deploy UI labels from "edited by" to "run as" for triggers
Triggers now use permissioned_as (not edited_by) for permissions, so
update the deploy UI wording to reflect this. Also update wm_deployers
group description to mention schedules and permissioned_as.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use u/username format for custom trigger/schedule deploy selection
When picking a custom user for trigger/schedule deployment, store
u/${username} (permissioned_as format) instead of the email. Flows/scripts
continue to use email format for on_behalf_of_email.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show u/username format for "me" option in trigger deploy selector
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: simplify OnBehalfOfSelector to return the right format per kind
OnBehalfOfSelector now handles the email vs permissioned_as format
internally based on kind:
- triggers: returns u/username, displays u/username in all options
- flows/scripts/apps: returns email, displays username
The onSelect callback now takes (choice, value?) where value is already
in the correct format. Parent components just store it directly without
needing to know about the format difference.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: always show u/username format in OnBehalfOfSelector for all kinds
Display is now consistent: all kinds show u/username in the selector.
The returned value still differs (email for flows/scripts, u/username
for triggers) since the backend APIs expect different formats.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace email with permissioned_as in http_trigger test insert
The email column was dropped from trigger tables in the migration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: review fixes — migration, app policy, capture cleanup, naming
- Migration: remove DEFAULT '', use nullable → populate → SET NOT NULL
- App policy: set both on_behalf_of and on_behalf_of_email for all choices
- OnBehalfOfSelector: return OnBehalfOfDetails {email, permissionedAs} instead of ambiguous value
- Remove unused email field from Capture struct and query
- Rename getSourceEmail/getTargetEmail → getSourceOnBehalfOf/getTargetOnBehalfOf
- Rename test functions from preserve_email to preserve_permissioned_as
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add permissioned_as to all test schedule INSERTs
Since the migration no longer uses DEFAULT '', all INSERTs must
explicitly provide permissioned_as. Updated test fixtures and
schedule_push tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: strip permissioned_as from exports/sync, fix OpenAPI required field
- Add permissioned_as to workspace export strip list (like edited_by)
- Add permissioned_as to CLI TriggerFile Omit list
- Fix TriggerExtraProperty.required: email → permissioned_as
- Regenerate frontend and CLI types
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove accidentally committed generated files
These directories are gitignored and should not be tracked.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts for permissioned_as schema changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove permissioned_as from CLI TriggerFile Omit list
Already stripped in workspace export, no need to also omit from the type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: optimize email cache key and revert TriggerFile Omit change
- Use single concatenated string for cache key instead of (String, String) tuple
- Remove permissioned_as from CLI TriggerFile Omit (already stripped in export)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: zero-allocation email cache lookups using Equivalent trait
Use a borrowed EmailCacheKey(&str, &str) for cache lookups via
quick_cache's Equivalent support. Only allocates (String, String)
on cache miss for insert. This is called on every trigger fire
and schedule push.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add permissioned_as to Schedule required fields in OpenAPI spec
The backend always returns permissioned_as (non-optional String),
so the schema should reflect that.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: handle group- prefix in migration UPDATE statements
edited_by can be 'group-{name}' for group-owned triggers/schedules.
The migration now correctly maps these to 'g/{name}' format instead
of incorrectly producing 'u/group-{name}'.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Revert "fix: handle group- prefix in migration UPDATE statements"
This reverts commit 0971392b38.
* fix: use superadmin email to resolve permissioned_as in schedule migration
For users upgrading from older versions where edited_by may not reflect
the actual schedule owner, check if the email belongs to a superadmin
and look up their username. Otherwise fall back to edited_by.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: fall back to superadmin email when not in workspace usr table
If the superadmin isn't a member of the workspace, use their email
as raw permissioned_as instead of falling back to edited_by.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: always update permissioned_as and email on schedule edit
Consistent with pre-refactor behavior where email and edited_by
were always updated on every edit. permissioned_as is now always
set (to editing user or preserved value), removing the COALESCE
that previously preserved it when not provided.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add schedule permission tests and centralize group prefix constants
Tests: schedule create/update for normal user, workspace admin, and
superadmin not in workspace. Verifies schedule fields (email,
permissioned_as, edited_by) and pushed job fields (permissioned_as,
permissioned_as_email).
Constants: centralize "u/", "g/", "group-" as PERMISSIONED_AS_USER_PREFIX,
PERMISSIONED_AS_GROUP_PREFIX, USERNAME_GROUP_PREFIX.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use @unknown.windmill.dev for synthetic email fallback
Prevents privilege escalation: a user with username like
'superadmin_secret' would get superadmin via the synthetic
email matching SUPERADMIN_SECRET_EMAIL. Using a different
subdomain avoids any collision with hardcoded @windmill.dev emails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* update ee ref
* sqlx
* chore: regenerate system prompts after main merge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to bda51bc33bcb573659e7ff07d0a23ff6e23b8148
This commit updates the EE repository reference after PR #468 was merged in windmill-ee-private.
Previous ee-repo-ref: 8cf1802f8fe183f430830590b4f3172a50207843
New ee-repo-ref: bda51bc33bcb573659e7ff07d0a23ff6e23b8148
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: extract McpScopeSelector into reusable component
Extract scope selection UI from CreateToken.svelte and mcp_authorize page
into a shared McpScopeSelector.svelte component to reduce duplication.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add MCP gateway endpoint for workspace-agnostic access
Add /api/mcp/gateway endpoint that allows MCP clients to connect without
knowing the workspace ID upfront. During OAuth, the user picks their
workspace on the consent page. The token is then scoped to that workspace.
This enables a single URL for the Anthropic connectors directory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review nits
- Use onClick prop instead of legacy on:click directive in McpScopeSelector
- Remove unused catch variable in workspace loading
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate gateway OAuth handlers into shared inner functions
Extract build_oauth_metadata, build_protected_resource_metadata,
oauth_authorize_inner, and oauth_approve_inner so gateway handlers
are thin wrappers. Also revert formatting-only changes in auth.rs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: gate run_inline test helpers behind cfg(feature = "run_inline")
Imports and helper functions were not gated, causing unused-import and
dead-code errors when compiling without the run_inline feature.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update SQLx metadata
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: handle OSS onboarding error gracefully in setup wizard
When creating a custom admin account fails on OSS builds (Enterprise-only
feature), show a helpful dialog instead of a generic error, guiding the
user to continue with default credentials.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use more precise error check for OSS account creation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct error message — not an EE feature, just not implemented in OSS
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove misleading "change from user settings" since set_password is also OSS-stubbed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: move default credentials info to frontend dialog only
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: full code apps deployable on merge UI and deploy UI
* update ee repo ref
* preapare sqlx
* split app and raw_app
* update eereporef
* fix displayy showing raw apps appropriately
* chore: update ee-repo-ref to b3b8005d45e3f2aa7228c61d2e4ae86a17d89a30
This commit updates the EE repository reference after PR #470 was merged in windmill-ee-private.
Previous ee-repo-ref: 78d1f6cc4b15ec4c0768969635ba6b8f166a7742
New ee-repo-ref: b3b8005d45e3f2aa7228c61d2e4ae86a17d89a30
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: resolve blank inline script panel for components with underscores in ID
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* perf: compute matched grid item once per selection instead of per-item
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: use WM_BACKEND_PORT/WM_FRONTEND_PORT env vars as port fallbacks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: use BACKEND_PORT/FRONTEND_PORT instead of WM_ prefixed vars
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: keep app.windmill.dev as ws proxy fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add gateway reverse proxy for extra services
Add a lightweight Node.js gateway on port 3000 that routes requests
by URL prefix (/ws/*, /ws_mp/*, /ws_debug/*) to the correct backend
service, stripping the prefix before forwarding. This allows all
extra services to be accessed through a single port.
Also makes the multiplayer server more tolerant by generically
stripping /ws_mp/ prefix on HTTP requests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: enable gateway by default for extra services
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add REMOTE_EXTRA env var for unified extra services proxy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: make gateway port configurable via PORT env var
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: simplify Caddyfile extra services routing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three issues fixed:
- Dragged element clone was invisible because morphDraggedElementToBeLike
ran before the clone was in the DOM, copying 0-height from the
uninitialized ResizeTransitionWrapper shadow. Fixed with morphDisabled.
- Shadow placeholder was inconsistently hidden because the DND library's
inline visibility:hidden was overwritten by RTW's reactive style binding.
Fixed with !visible CSS class that overrides inline styles.
- Small cursor movements immediately triggered field reordering. Added a
200ms grace period after drag start before processing reorder events.
The shadow element now shows a dashed blue drop-target indicator instead
of being fully hidden.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] feat: add OTel metrics support
Add OpenTelemetry metrics export for Windmill operational metrics.
When the OTel metrics toggle is enabled in instance settings (EE),
Windmill exports 16 metrics to any OTLP-compatible collector, letting
users observe queue depths, worker execution, DB pool state, and health
without a separate Prometheus setup.
Changes:
- otel_oss.rs: no-op stubs for OSS builds
- monitor.rs: queue count/running count gauges, zombie counters, DB pool
monitoring (shared single DB query and loop with Prometheus)
- worker.rs: execution count/duration, worker busy, pull duration
- jobs.rs: queue push/delete/pull counters
- health.rs: DB latency gauge
- main.rs: call monitor_pool_otel unconditionally
- InstanceSetting.svelte: enable metrics toggle for EE licenses
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt for OTel metrics
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add worker_started, worker_uptime, health_status, health_db_unresponsive OTel metrics
Wire up 5 additional metrics to reach parity with Prometheus:
- worker_execution_failed: wired in add_completed_job_error (was defined but unused)
- worker.started: incremented on worker startup
- worker.uptime: recorded each loop iteration
- health.status: phase gauge (healthy/degraded/unhealthy)
- health.db_unresponsive: flag (0/1)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to fbe68e4aa621e30378995cfd328a6ccf74176614
This commit updates the EE repository reference after PR #469 was merged in windmill-ee-private.
Previous ee-repo-ref: 6fa1881aafdfb60f4abf11a37f01f6fedaecb3ec
New ee-repo-ref: fbe68e4aa621e30378995cfd328a6ccf74176614
Automated by sync-ee-ref workflow.
* fix: remove duplicate cfg attr and duplicate OTel pool reporting
- Remove duplicate #[cfg(feature = "prometheus")] on monitor_pool
- Remove OTel block from monitor_pool; monitor_pool_otel is the sole
OTel reporter, eliminating duplicate windmill.db.pool.* metrics in
EE builds
- Simplify monitor_pool back to its original Prometheus-only structure
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: track schema.properties reference not keys in EditableSchemaForm
Object.keys() tracked key enumeration, so renaming a field triggered
onSchemaChange -> alignOrderWithProperties -> reorder. schema?.order
created a feedback loop since alignOrderWithProperties writes to it.
Only schema?.properties (the object reference) is needed to detect
when inferArgs replaces properties (schema.properties = {}).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: revert EditableSchemaForm effect to original
The added schema?.order and Object.keys(schema?.properties) tracking
caused field reordering on rename — Object.keys returns the renamed
key at the end (JS insertion order after delete+add), and schema?.order
created a feedback loop with alignOrderWithProperties. Revert to the
original schema reference-only tracking.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three root causes:
1. Editor.setCode() never dispatched `change` — it pre-set `code = ncode`
before the Monaco edit, so the debounced updateCode() saw code == ncode
and skipped dispatch. The Reset button, copilot accept, and other
setCode callers never triggered schema inference. Fixed by capturing
`changed` before the pre-set and dispatching directly when true.
2. EditableSchemaForm's $effect only tracked the schema reference, not
its properties. Since inferArgs mutates schema in-place through the
Svelte 5 proxy, the reference never changes and the effect never
re-ran. Added schema?.order and Object.keys(schema?.properties ?? {})
reads to detect in-place mutations (matching SchemaForm's pattern).
3. ScriptEditor's $effect depended on both selectedTab and code, causing
a redundant double inferSchema call on every code change (racing with
the on:change handler and initContent's explicit call). Moved code
into untrack() so the effect only fires on tab switches.
Also removed the no-op `testPanelSchema = testPanelSchema` in
inferModuleSchema.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VirtualList crashes with 'Requested index 0 is outside of range 0..0'
when it mounts with itemCount=0 and a positive height. This happened
because the old condition allowed VirtualList to remount during loading
with zero items but a stale listDivHeight from a previous mount.
Change the guard to displayedFileKeys.length === 0 so VirtualList is
never rendered when there are no items. Show a centered loading spinner
or "no files" message instead.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent AI agent tool jobs from becoming zombies on cancellation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* kill tool tasks on cancel timeout
* fix: address review feedback and update sqlx cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: MCP server readiness for Anthropic integrations directory
- Add CORS layer to MCP streamable HTTP endpoint for browser clients
- Add tool result truncation (25K token limit) to prevent oversized responses
- Add HEAD method support on OAuth authorize endpoint
- Skip workspace selection redirect during MCP OAuth flow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR #8438 review feedback
- Add DELETE to CORS allowed methods (MCP spec requires DELETE for session termination)
- Add CORS layer to deprecated /sse endpoint for completeness
- Remove redundant .head() on OAuth authorize (axum auto-handles HEAD via GET)
- Fix comment: "chars/token" → "bytes/token" since len() returns bytes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: show cancelled WAC jobs as done in workflow timeline
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: keep polling after cancel so WAC timeline updates to completed state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: treat WAC as done in LogPanel when loader stops after cancel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show preview badge and hide _MODULES arg in run history
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show preview badge alongside status dot, not instead of it
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add cloud quota usage display and version pruning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hard-delete pruned scripts so quota actually decreases
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: update quota error messages to reference workspace settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cli): use local scripts when previewing flows
When previewing a flow, PathScript modules (type: "script") now resolve
to local file content instead of remote versions. This ensures flow
preview and dev mode test the actual local changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(cli): add tests for PathScript local replacement in flow preview
Unit tests for replacePathScriptsWithLocal covering:
- basic PathScript→RawScript conversion
- tag_override preservation
- missing local file fallback
- mixed module types
- nested structures (loops, branches)
Integration test verifying flow preview with a PathScript step
uses the local script file content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(cli): extract shared helpers and add aiagent support for PathScript replacement
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(cli): replace `as any` casts with proper type assertions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cli): preserve local flow preview script context
* fix(cli): normalize inline flow preview bundles for bun
* fix(cli): make local flow path scripts opt-in
* fix(cli): only merge flow preview config for local mode
* chore(system-prompts): regenerate cli command guidance
* fix(cli): skip deno defaultTs test in CI without deno runtime
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(cli): clean up local path script helpers
* feat(cli): make flow preview use local path scripts
* fix(cli): ignore normalized preview metadata drift
* chore(cli): address review follow-ups
* test(cli): cover custom bundler path quoting
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* perf: cache composer vendor dir to skip reinstall on repeated php executions
* feat: add COMPOSER_VENDOR_CACHE_DISABLED env var to opt out of vendor caching
---------
Co-authored-by: hugocasa <hugo@casademont.ch>
- Fix debugger HTTP health endpoint to also match /ws_debug/health
(ingress forwards the full path, not just /health)
- Add request logging to all three extra services (LSP, multiplayer,
debugger) for HTTP and WebSocket ping/upgrade events
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(frontend): remove banned $bindable('') pattern from ClearableInput
Switching format types in the flow input editor caused a
props_invalid_value error because ClearableInput used
value = $bindable(''), which conflicts with undefined bindings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(frontend): restore flow result display in result node
The fix in #8390 changed updateLastJob() to only use testJob when
actively running/streaming, preferring flowStateStore for completed
results. But the result node has moduleId='' and no flowStateStore
entry, so the early return made it always show the empty state.
Add !moduleId to the testJob condition so the result node (which has
no flowStateStore entry) still uses testJob as its only data source.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When switching to a non-main module tab, the test panel now infers
args from the module's code and runs the module's code on Test/Cmd+Enter.
Per-module args and schema are persisted across tab switches.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] improve analytics: add git sync & AI chat telemetry, HMAC-signed download
- Add ai_chat_usage table to track chat sessions (session_id, provider, model, mode, message_count)
- Add POST /w/{workspace}/workspaces/log_chat endpoint with upsert on session_id
- Frontend fires logAiChat on every sendRequest, using HistoryManager's existing chat ID
- EE stats: add git_sync_usage (sync vs promotion repo count) and ai_chat_usage (30-day aggregates)
- Replace RSA+AES-GCM encrypted telemetry download with plaintext JSON + HMAC-SHA256 signature
- Signature (12 hex chars) included in download filename for verification
- Update instance settings telemetry descriptions for both EE and CE
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make StatsDownload struct pub to fix private-interfaces error
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 878cc2044717e0177228529a50433fe2768e70b5
This commit updates the EE repository reference after PR #464 was merged in windmill-ee-private.
Previous ee-repo-ref: 33eb863b6b881bd54ed69a540e0c65d5fe125024
New ee-repo-ref: 878cc2044717e0177228529a50433fe2768e70b5
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat(cli): add --env alias for --branch and environments config alias
Add --env as a CLI alias for --branch on sync pull, sync push, workspace
bind, and workspace unbind commands. Add environments as a permanent
config alias for gitBranches in wmill.yaml. This helps users who use
single-branch multi-environment workflows where "branch" terminology
is confusing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate auto-generated system prompts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update cloudformation template to use latest cli/images + fix cleanup script
* fix: narrow SG cleanup to k8s-created groups + add CLI install error handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add 93 missing integration icons and fix dark mode visibility
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add 11 more integration icons (round 2)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add 5 more integration icons (round 3)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: make instance events webhook URL configurable via superadmin UI
The instance events webhook was previously only configurable via the
INSTANCE_EVENTS_WEBHOOK env var, requiring a restart to change. This
adds a DB-backed global setting with a UI in superadmin settings under
Monitoring > Webhooks, while keeping the env var as an override.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review - prometheus timer bug and cleaner cache init
- Bind prometheus timer to `let timer` and call `stop_and_record()`
after the POST (was silently discarded before)
- Use `Option<Instant>` with `map_or` instead of `checked_sub` trick
for clearer "not yet read" semantics
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove env var mention from webhook setting description
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: list all instance events explicitly in webhook description
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore send_instance_event guard with AtomicBool for DB setting
Use a shared Arc<AtomicBool> between send_instance_event and the event
loop so we skip channel sends when no webhook is configured (env or DB).
Starts optimistic (true) so the first event triggers a DB read, then
the loop updates it after each cache refresh.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use static AtomicBool + notify handler for webhook guard
Replace the Arc<AtomicBool> instance field with a global static
INSTANCE_EVENTS_WEBHOOK_DB_ENABLED, updated by the
notify_global_setting_change handler in main.rs. This follows the
established pattern (like REQUIRE_PREEXISTING_USER_FOR_OAUTH) and
avoids the deadlock where the bool could never flip back to true.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: single Arc<RwLock<Option<String>>> for instance webhook URL
Replace the separate INSTANCE_EVENTS_WEBHOOK env var lazy_static and
INSTANCE_EVENTS_WEBHOOK_DB_ENABLED AtomicBool with a single shared
variable. Initialized from env var, then the reload function overwrites
from DB (falls back to env var when DB has no value). Follows the same
pattern as SCIM_TOKEN and other settings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* feat: add workspace-specific registry overrides
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move workspace registries to end of registries tab
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: workspace overrides use field selector instead of showing all fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: polish workspace registries UI to match design guidelines
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: show field selector directly and fix addField initialization logic
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: namespace pip_resolution_cache by workspace when registry overrides exist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: namespace binary/bundle caches by workspace when registry overrides exist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* perf: zero-cost cache suffix when no workspace overrides exist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: reload workspace_registries via notify events on setting change
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review findings
- Fix discardCategory not reverting workspace_registries changes
- Fix get_no_default: convert to async fn with owned Uuid param
- Fix append_logs: use windmill_queue import already available
- Fix ruby URL parsing: support both comma and whitespace delimiters
- Add WorkspaceRegistryMap type alias to reduce inline type noise
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* all
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add ws_base_url instance setting to override WebSocket base URL
Allow deployments behind reverse proxies to route WebSocket traffic
(LSP, debugger, multiplayer) to a different host/port than the main
frontend via a new instance setting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: move ws_base_url to Advanced section with toggle and connectivity test
- Move setting from Core to Advanced > WebSocket section
- Render as toggle "Custom websocket base url from frontend to
multiplayer/lsp/debugger" with conditional URL text field
- Add Test connectivity button (always visible) that checks HTTP health
and WebSocket ping for all three services (LSP, Multiplayer, Debugger)
- Add /ws/ping and /ws/health endpoints to LSP service
- Add /ws_mp/health HTTP and __ping__ WS handlers to multiplayer service
- Add /ping WS handler to debugger service
- Add CORS headers to health endpoints for cross-origin testing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: toggle enabled check and testWs promise resolution
- Fix enabled derived to check only for null (not empty string),
otherwise the toggle never turns on since toggleEnabled sets ''
- Fix testWs onclose handler to resolve(false) so the promise
doesn't hang if the server closes without sending a message
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make connectivity test work with existing services
- HTTP test: accept plain text "ok"/"okay" (old services) in addition
to JSON {"status": "ok"} (new services), reject HTML (SPA fallback)
- WS test: resolve on onopen (connection established) instead of
waiting for a specific pong message, so the test works even with
services that don't have the new /ping handler yet
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add script module mode with folder model for Bun and Python
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add missing modules field to RawCode in bun_executor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* sqlx
* feat: enrich WAC templates with checkpoint and replay semantics
Add prominent comments explaining that all computation must happen
inside task/step/taskScript or it will be replayed on resume/retry.
Clarify that waitForApproval does not hold a worker and that
approve/reject URLs are available in the timeline step details.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cli): script module sync idempotency, per-module hash tracking, and preview support
- Fix pull→push idempotency: use `??` instead of `||` for module lock
field so empty strings are preserved (matches API's `lock: ""`)
- Add per-module hash tracking in wmill-lock.yaml following the flow
inline script pattern (SCRIPT_TOP_HASH + per-module subpath hashes)
- Selective module lock regeneration: only regenerate locks for modules
whose content actually changed, not all modules
- Use unfiltered rawWorkspaceDependencies for module hashes to match
what updateModuleLocks passes to fetchScriptLock
- Show changed module names in stale script output for clarity
- Add module support to `script preview` command: read modules from
__mod/ folder and pass them in the preview API request
- Add preview tests for taskScript pattern (flat and folder layout)
- Update test assertion for module stale detection output
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(frontend): WAC UI improvements — reorder templates, module tab rename, import consolidation
- Reorder WAC template buttons: TypeScript before Python in
ScriptBuilder, CreateActionsScript, and CreateActionsFlow
- Remove dropdown items from +Script button (simplify to direct link)
- Move "Import Workflow-as-Code" to +Flow dropdown with dedicated drawer
- Add module tab rename: pencil icon on hover opens popover with
validation, fixed-width icon container prevents layout shift
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: remaining module-mode changes from working branch
- Backend parser updates for WAC detection
- CLI sync/types updates for raw app path and module support
- Frontend UI polish (Dev.svelte, ScriptRow, script hash page)
- Test fixture updates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(cli): add test for module modification detection in generate-metadata
Verifies that modifying a single module file re-triggers stale
detection and only the changed module is listed, not all modules.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(backend): critical fixes from PR review
- Fix hardcoded dev path in bun_executor.rs WAC v2 wrapper — use
"windmill-client" import instead of absolute filesystem path
- Fix missed no_main_func → auto_kind rename in parser TS test
- Add modules column to clone_script SQL (windmill-common and
windmill-api-workspaces) so cloned scripts retain their modules
- Add modules: None to RawCode structs in worker tests
- Restore complete sqlx cache (merge main's cache + our new queries)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(backend): fix clone warning treated as error in CI
Change `.clone()` on double reference to `*k` dereference in
scripts.rs hash implementation. Update sqlx cache with new query
hashes from modified clone_script SQL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(frontend): use published parser wasm versions for CI build
The local file:// paths for windmill-parser-wasm-py and
windmill-parser-wasm-ts don't exist in the Cloudflare Pages build
environment. Revert to published npm versions (1.655.0).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(frontend): update parser wasm packages to 1.657.2
Use newly published windmill-parser-wasm-ts and windmill-parser-wasm-py
v1.657.2 which include auto_kind/WAC detection changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(frontend): regenerate package-lock.json for npm ci compatibility
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(frontend): use main's lockfile as base, update only parser wasm packages
Regenerating package-lock.json from scratch pulled different dependency
versions causing svelte-check type errors. Instead, start from main's
lockfile and only update the two changed packages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(backend): add modules column to fetch_script_for_update query
The Script<SR> struct has a modules field (FromRow), but
fetch_script_for_update didn't SELECT modules, causing a runtime
error "no column found for name: modules" when the worker processed
dependency jobs. This was the root cause of the relock_skip test
timeout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(backend): fix script module execution for Python and Bun
- Fix modules not passed through job queue: inject _MODULES into
PushArgs.extra when pushing Code jobs so worker can extract them
- Fix Python module imports: use relative imports (from .helper)
and add sys.path.insert for module directory in wrapper
- Fix Python tests: use relative imports and empty lock to prevent
pip from resolving module names as packages
- Add local file check in Bun loader for module resolution
- Ignore Bun module test (bundle mode loader integration tracked
separately)
- Add missing modules column to fetch_script_for_update query
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(backend): remove unnecessary empty lock in Python module tests
Relative imports (from .helper) are not parsed as pip packages,
so the empty lock workaround is not needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(backend): fix module execution for Python and Bun — all tests pass
Python modules:
- Use relative imports (from .helper import greet) since scripts run
as packages
- Add sys.path.insert for module directory in wrapper to ensure local
modules take precedence over pip packages with same name
Bun modules:
- Use bundled output (./out/main.js) as wrapper import when modules
are present — the bundled output has module content inlined by
Bun.build, avoiding runtime loader resolution issues
- Add local file check in loader.bun.js onResolve to short-circuit
API URL resolution for module files on disk
Job queue:
- Inject _MODULES into PushArgs.extra when pushing Code jobs so
the worker can extract them at execution time
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: address PR review — simplify, fix correctness, remove dead code
Critical fixes:
- Replace all CLI `no_main_func` references with `auto_kind` (string)
to match the backend migration and API changes
- Remove duplicated `compute_python_module_dir` in worker.rs, use
the canonical version from python_executor.rs
High priority:
- Auto-create `__init__.py` in intermediate directories for nested
Python modules so imports like `from .utils.math import add` work
without users manually creating __init__.py files
- Remove redundant `sys_path_insert` — relative imports use Python's
package system, not sys.path
Medium:
- Fix lock file base name extraction: use regex to strip only the
final extension (`.replace(/\.[^.]+$/, '')`) instead of `indexOf(".")`
which breaks for files like `helper.test.ts`
Simplification:
- Remove dead `{#if false}` Popover block in ScriptEditor.svelte
- Guard loader.bun.js local file check to only run for relative paths
(matching the Windows loader pattern)
- Add clarifying comment on Bun dual mechanism (build + run phases)
- Add maintenance comment on manual Hash impl for NewScript
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: final review fixes — stale cleanup, baseName, auto_kind export
- Fix sync.ts baseName extraction using indexOf(".") → regex
(same fix as script.ts/metadata.ts, missed this instance)
- Add stale module file cleanup in writeModulesToDisk: removes files
from __mod/ that are no longer in the modules map before writing,
fixing the pull→push cycle that couldn't delete modules
- Log warning when _MODULES serialization fails in job push instead
of silently dropping modules
- Use strict equality (===) for auto_kind comparison
- Exclude auto_kind from workspace export — it is auto-detected by
the parser at deploy time from script content
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): remove auto_kind from push, comparison, and metadata
auto_kind is auto-detected by the parser at deploy time, so the CLI
should not send it, compare it, or write it to script.yaml.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove erroneously added backend/backend/.sqlx directory
Duplicate .sqlx cache was committed at the wrong nested path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review feedback + fix CI dead_code warning
Frontend (ScriptEditor.svelte):
- Fix switchToMain() missing lastSyncedCode update — prevents stale
code sync on external changes while editing a module tab
- Fix formatAction saving module code to main script's localStorage
draft — now saves main code when on a module tab
- Fix non-null assertion on inferModuleLang in renameModule — fall
back to original language instead of force unwrap
- Remove redundant activeModuleTab truthy check in runTest
CLI (script.ts):
- Clean up empty directories after removing stale module files in
writeModulesToDisk
Backend:
- Add path traversal guard in write_module_files — reject module
paths containing ".."
- Fix dead_code warning on auto_kind field in workspace export struct
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(frontend): improve auto_kind UX + address review findings
- Rename "Include without main function" toggle to "Include library
scripts" in script list (ItemsList.svelte)
- Update NoMainFuncBadge: "No main" → "Library" with clearer tooltip
- Filter module file extensions by main script language — Python
scripts only allow .py modules, TypeScript only .ts, etc.
- Split flushModuleState into flushModuleContent (no UI side-effect)
and flushModuleState (flush + reset tab), reducing duplication
- Dynamic placeholder and hint text in add module popover based on
main script language
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* [ee] feat: instance groups instance-level role support
Add ability to assign instance-level roles (superadmin/devops) to
instance groups. Group members automatically receive the role with
proper precedence: manual elevated roles always win, superadmin > devops.
- Migration: add instance_role to instance_group, role_source to password
- Role propagation on all mutation paths (add/remove/update/delete/import)
- SCIM sync integration (companion PR: windmill-ee-private#463)
- Frontend: role toggle in group editor, role column in tables,
role source indicator in superadmin settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to 278a3887f759f9d1146554baa0765518d5bc70f2
This commit updates the EE repository reference after PR #463 was merged in windmill-ee-private.
Previous ee-repo-ref: b407fe4604153d09ff223e11d43c2df83a1de5d0
New ee-repo-ref: 278a3887f759f9d1146554baa0765518d5bc70f2
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <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: store hashed tokens in the token table instead of plaintext
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review issues in token hash migration
- Update all base.sql fixtures to include token_hash/token_prefix columns
- Keep plaintext token for webhook tokens (needed for URL reconstruction)
- Restore get_token_by_prefix to query DB for webhook tokens
- Fix down migration to delete NULL-token rows before restoring NOT NULL
- Update parser fixture standalone schema
- Update EE dedicated_worker_ee.rs to use token_hash/token_prefix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore sqlx offline cache (only add new query files)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: keep writing plaintext token column for backward compat
Write to token column alongside token_hash until MIN_VERSION_SUPPORTS_TOKEN_HASH
(1.649.0) is reached. This ensures older workers can still authenticate
during rolling upgrades. Remove the separate UPDATE in new_webhook_token
since create_token_internal now writes plaintext directly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: branch on MIN_VERSION to write plaintext token or null
Check MIN_VERSION_SUPPORTS_TOKEN_HASH at runtime: write plaintext to
token column while old workers exist, switch to NULL once all workers
are >= 1.649.0.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: set MIN_VERSION_SUPPORTS_TOKEN_HASH to 1.650.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use token_hash for email lookup and expiry notifications
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: rotate webhook tokens instead of recovering plaintext from DB
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: use token_hash for native trigger token lookups and deletes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* sqlx
* refactor: drop webhook_token_prefix from native_trigger table
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: backward compat for token rotation and make webhook_token_hash NOT NULL
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: prevent panic on short superadmin secret token prefix
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: prevent panic on short superadmin secret token prefix
Replace all `token[0..TOKEN_PREFIX_LEN]` slicing with
`token.get(..TOKEN_PREFIX_LEN).unwrap_or(token)` to prevent
panics when a token shorter than 10 chars is provided (e.g.
malformed Authorization header, short superadmin secret).
Co-authored-by: hugocasa <hugocasa@users.noreply.github.com>
* fix: prevent panic on short token prefix slicing
Replace all `token[0..TOKEN_PREFIX_LEN]` with safe
`token.get(..TOKEN_PREFIX_LEN).unwrap_or(token)` to prevent panics
on malformed tokens shorter than 10 characters.
Co-authored-by: hugocasa <hugocasa@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert "fix: prevent panic on short superadmin secret token prefix"
This reverts commit 37ec2e5ad5.
* revert: remove unnecessary defensive token prefix slicing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add token_hash to end_user_email test fixture
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: add integration tests for token hash migration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: correct token_hash test assertions for cache and version
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: add plaintext column removal reminder to test fixtures
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: log count of orphaned triggers deleted during migration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: preserve orphaned triggers with error instead of deleting
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: rename token_expiry_notification.token to token_hash and copy owner/expiration in rotate
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: hash existing plaintext values before renaming token_expiry_notification column
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: remove unnecessary length check in token_expiry_notification migration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* update dates and version
* updat ee ref + sqlx
* improve mcp migration
* fix: atomic token rotation with rollback on trigger update failure
rotate_webhook_token now atomically inserts the new token and deletes
the old one in a single transaction, preventing token leaks.
Returns new_token_hash so callers can clean up the new token if their
subsequent trigger update fails (which involves external HTTP calls
and cannot be in the same DB transaction).
- Handler: wraps post-rotation work; deletes new token on failure
- Google renewal: deletes new token if service_config update fails
- Tests updated to match new atomic semantics
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* higher min version
* fix: defer old token deletion to avoid breaking triggers on update failure
rotate_webhook_token now keeps the old token alive and returns
old_token_hash. Callers delete it only after the trigger row has been
successfully updated. If the external service call or DB update fails,
the trigger keeps working with the old token.
Worst case: if the best-effort delete fails, the old token leaks as an
extra DB row — harmless compared to breaking the trigger.
Also update summarized_schema.txt for renamed columns.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 2d0823a471014e2bc2d898c63518323946b7474f
This commit updates the EE repository reference after PR #437 was merged in windmill-ee-private.
Previous ee-repo-ref: 7aef8b06cb6f54c2bc89dd57b70947deed72553c
New ee-repo-ref: 2d0823a471014e2bc2d898c63518323946b7474f
Automated by sync-ee-ref workflow.
* fix: prevent panic on short tokens by using safe prefix extraction
Add safe_token_prefix() helper that uses .get(..TOKEN_PREFIX_LEN).unwrap_or(token)
instead of direct slice indexing, preventing panics when tokens are shorter than
10 characters (e.g., short superadmin secrets or malformed Bearer tokens).
Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: HugoCasa <hugo@casademont.ch>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: hugocasa <hugocasa@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>
* fix: improve OOM killer observability for debugging pod-level kills
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: lower worker oom_score_adj to protect it from OOM killer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review feedback on OOM observability
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add end_user_email claim to OIDC ID tokens
When a job is triggered by an app end user, the OIDC ID token now
includes the end_user_email claim automatically. The claim is omitted
for jobs without an end user (regular script/flow runs).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* update ee ref
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: return structured error object on AI agent max iterations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: avoid double serialization in max iterations error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: replace unwrap with ? for to_raw_value in max iterations error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add step_id to max iterations error for consistency with SerializedError
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
EE files (*_ee.rs, *_ee.ts, *_ee.svelte) are symlinks from
windmill-ee-private that are gitignored. The walker skipped them
because it respects .gitignore. This adds a separate recursive scan
for _ee files and merges them into the index. Also fixes outline
resolving symlinks via canonicalize, causing path mismatches.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add workspace dependencies support for powershell
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: improve workspace deps editor UX for powershell
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add powershell workspace deps support to CLI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(native-triggers): preserve API error response body in HttpRequestError
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(frontend): use instance credentials for reconnect when instance_shared
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* chore: split debug info for EE release builds
Generate line-table debug info in release builds and split it into
a separate .debug file. The shipped binary remains stripped (same
size as before), while the .debug files are attached to GitHub
releases for both amd64 and arm64 EE builds.
This enables production debugging with gdb/perf by copying the
matching .debug file into a running pod.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: extract debug info via separate Docker stage
Use a `FROM scratch AS debuginfo` stage instead of copying the .debug
file to the final image. This keeps the shipped image at exactly the
same size as before. CI extracts the .debug file using depot's
--target debuginfo with cache hits from the main build.
Also adds gnu_debuglink so gdb auto-discovers the debug file when
placed next to the binary.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ee] feat: add GET /api/saml/metadata endpoint for SP metadata XML
Serves static SAML 2.0 EntityDescriptor XML so IDPs can configure SSO
without requiring IDP metadata to be set up first in Windmill.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to b2fde51087d0d0ee0223c15cafb4e8badddd2d13
This commit updates the EE repository reference after PR #461 was merged in windmill-ee-private.
Previous ee-repo-ref: 187f12947b69e584523ace93957d0be0ceb7b37c
New ee-repo-ref: b2fde51087d0d0ee0223c15cafb4e8badddd2d13
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: soft error when AI agent reaches max iterations instead of hard error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: set output to error message when AI agent reaches max iterations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add error field to AI agent result for max iterations soft error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore default max iterations to 10
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: include partial result in max iterations error message
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: rename retries tab to error handling and reorganize sections
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: support multiple secret variables during resource creation
When creating a resource, users can now select multiple fields to be
stored as secret variables. If only one field is selected, behavior
is unchanged (single variable with same path as resource). If multiple
fields are selected, each gets its own variable with _field_name appended
to the resource path.
Closes#8384
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: delete all linked secret variables when resource is deleted
When a resource with multiple secret fields is deleted, also delete
variables matching the {path}_{field_name} pattern in addition to
the exact path variable. Each deleted variable gets its own deployment
metadata update and webhook notification.
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update backend/windmill-store/src/resources.rs
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
* fix: only delete linked variables that are actually referenced in resource value
Instead of deleting variables purely based on path prefix matching
(which could accidentally delete unrelated variables), the deletion
now reads the resource value first, extracts all $var: references,
and only deletes variables that are actually used in the resource.
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Diego Imbert <diegoimbert@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Diego Imbert <70353967+diegoimbert@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
The /updatesqlx workflow was checking out windmill-ee-private at its
default branch HEAD, ignoring the specific commit pinned in
backend/ee-repo-ref.txt. This could cause sqlx metadata to be generated
against a mismatched EE version.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OutputPickerInner.updateLastJob() unconditionally returned testJob
(from individual step tests) even when flowStateStore had newer results
from a flow test. Now testJob only takes priority when a step test is
actively running/streaming; otherwise flowStateStore is the source of
truth.
Also reset stepHistoryLoader initial flags when a flow test completes
so the "Run loaded from history" indicator doesn't persist.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The enterprise feature was not being forwarded to windmill-api-schedule
in windmill-api's Cargo.toml, causing the #[cfg(not(feature = "enterprise"))]
guards in create_schedule to remain active even in EE builds. This made
on_recovery, on_success, and on_failure_times>1 incorrectly rejected
with "only available in enterprise version" for enterprise customers.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add nonDottedPaths convention to CLAUDE.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(cli): update generated skills to use non-dotted path conventions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): make generated skills respect nonDottedPaths config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): inject nonDottedPaths placeholders in generate.py for skills.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: default system prompts to non-dotted path conventions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(frontend): prevent duplicate and reserved agent tool names
Extend tool name validation to detect duplicates within an agent step
and reserved names (like 'preprocessor', 'failure'). Show specific error
messages in the editor panel and red styling in the graph view.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(frontend): remove duplicate banner for agent tool name errors
The inline per-tool error messages are sufficient — the panel-level
banner was redundant and showed a double error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resource drawer opening behind dialog in chat mode
Integrate Modal into the Disposable z-index stacking system so drawers
opened from within a modal (e.g. "Add a new resource") correctly appear
above the dialog instead of behind it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resource drawer opening behind dialog in chat mode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: simplify minZIndex tracking by removing unnecessary refcount
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use map-based minZIndex tracking and conditional chat elevation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use plain object instead of Map for reactive minZIndex tracking
$state(new Map()) is not deeply reactive in Svelte 5 — only plain
objects and arrays are proxied. Replaced with Record<string, number>
so that property assignments properly trigger $derived updates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cli): suppress verbose lock generation messages in generate-metadata
Pass noStaleMessage flag through to updateRawAppRunnables and
updateAppInlineScripts to suppress verbose "Generating lock for..."
messages when running generate-metadata command. Also fixes a stray
`}` character in a template literal.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(cli): show updated inline scripts in generate-metadata output
Display inline script names that were relocked when processing flows
and apps in the generate-metadata command output. For example:
[4/5] app u/admin/test__raw_app: a, b, c
This provides visibility into what work was done without verbose
per-script logging that clutters the output.
- Add AppLocksResult and FlowLocksResult types to track updated scripts
- Update internal functions to return lists of updated script names
- Display script names inline with progress in generate-metadata
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Detect union types in TS
* display union type arguments
* Handle single values at accumulation time
* nit propagate otyp
* Python support
* npm package update
The backend already filters tokens by scope matching the script/flow
path. Remove the redundant client-side label prefix filter so that all
tokens with matching scopes are shown, not just those with a specific
label convention.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Files inside .raw_app/backend/ were incorrectly being processed by
`script generate-metadata` and `generate-metadata --skip-flows --skip-apps`
because the filter only checked isFlowPath and isAppPath, but not isRawAppPath.
This caused backend runnables to be treated as standalone scripts, creating
incorrect .script.yaml files at wrong locations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: collapse flow topbar buttons to icon-only mode in narrow panes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: show delete button on top-right of compact error handler
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: preserve bug icon and diff action bar in compact error handler
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: avoid duplicate delete buttons when diff action bar is active
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: use undefined instead of empty string for wrapperClasses
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* chore: remove wm-cursor, add local-review skill, update PR skill for EE
- Remove the unused wm-cursor script and all references to it in
README_WORKMUX_DEV.md and worktree-common.sh
- Add /local-review skill for code review (bugs + CLAUDE.md compliance)
- Add EE companion PR workflow to the /pr skill
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add wm-ts-nav tree-sitter navigator and fix format hooks
- Add wm-ts-nav: standalone tree-sitter code navigator with SQLite index
for fast symbol search, definition lookup, and file outlines across
Rust, TypeScript, and Svelte files (~12ms warm, ~1s cold for 482 files)
- Fix format hooks: surface errors instead of swallowing with 2>/dev/null,
use direct prettier path with svelte plugin, add success feedback
- Add wm-ts-nav commands to settings allow list
- Document wm-ts-nav usage in CLAUDE.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(wm-ts-nav): add refs command and --parent filter
- refs: find usages of a symbol in code, skipping comments and strings
(tree-sitter AST walk, ~46ms for 482 files vs grep's 4ms but no noise)
- --parent filter on search: find all methods on a type across all files
(e.g. search "%" --kind function --parent ServiceName)
- Update CLAUDE.md with clearer when-to-use guidance
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(wm-ts-nav): index refs in DB with import-path resolution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(wm-ts-nav): add body, callers, callees commands and refs --file/--caller
- body: extract a symbol's source code from disk using indexed line ranges
- callers: cross-file call graph via SQL join of refs + symbols tables
- callees: list all identifiers referenced within a symbol's body
- refs --file: scope results to files matching a substring
- refs --caller: annotate each ref with the containing function name
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(wm-ts-nav): add auto-rebuilding wrapper script
The `wm-ts-nav/nav` wrapper checks if source files are newer than the
binary and rebuilds automatically. Invoked via `sh wm-ts-nav/nav` to
avoid needing executable permissions after clone.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: tighten CLAUDE.md nav section for actionable guidance
Remove redundant question→command mapping, latency numbers, and
excessive examples. Lead with "prefer wm-ts-nav over Read to save
context window" and keep only the patterns that change behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: revert backend/Cargo.lock to main
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: promote wm-ts-nav in workflow, copy binary to worktrees
- CLAUDE.md: integrate wm-ts-nav into Workflow step 1 and Core
Principles so agents use outline/body before full file reads
- workmux: copy built binary via files.copy
- worktree-common.sh: copy binary in wm_copy_dependencies for webmux
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(wm-ts-nav): fix double indexing, add TSX grammar, remove needless clone
- Reuse index stats from the pre-query update instead of indexing twice
on the Index command
- Add Lang::Tsx variant so .tsx/.jsx files use LANGUAGE_TSX instead of
LANGUAGE_TYPESCRIPT (Svelte stays on TS since script blocks are pure TS)
- Remove source.clone() for non-Svelte files — move directly instead
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(wm-ts-nav): fix svelte line numbers, add class methods, innermost caller
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add GitHub Enterprise Server (GHES) support for GitHub App git sync
Add a self-managed GitHub App mode alongside the existing managed
(stats.windmill.dev) mode, enabling git sync for GitHub Enterprise Server
and custom GitHub App installations.
Backend:
- Parameterize GitHub API URLs (no more hardcoded github.com)
- Add GITHUB_ENTERPRISE_APP_SETTING global setting
- Add OpenAPI specs for ghes_installation_callback and ghes_config endpoints
Frontend:
- Add instance settings UI for configuring self-managed GitHub Apps
with setup instructions and validation
- GHES installation flow in gh_success page
- Dynamic installation URL based on GHES config
- Increase git sync test connection timeout to 10s
- Block "Review changes" save when settings are invalid
EE companion PR: windmill-labs/windmill-ee-private#<PR_NUMBER>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to c74c86b78a66b976fd9968b21f77903723e668ec
This commit updates the EE repository reference after PR #459 was merged in windmill-ee-private.
Previous ee-repo-ref: 45e4550110799525b5502cf072c8af8132492638
New ee-repo-ref: c74c86b78a66b976fd9968b21f77903723e668ec
Automated by sync-ee-ref workflow.
* sqlx
---------
Co-authored-by: Claude Opus 4.6 <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(cli): add unified generate-metadata command
- Add generate-metadata command that calls script, flow, and app handlers
- Export generateLocks from flow.ts and generateMetadata from script.ts
- Add deprecation warnings to individual metadata commands
* feat(cli): improve unified generate-metadata command
- Use internal handlers for single-pass collection of stale items
- Add --dry-run flag to show what would be updated
- Fix WASM parser init deprecation warning
- Add comprehensive tests for all flags
- Match original handler behavior for per-item messages
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(cli): add skip flags and comprehensive tests for generate-metadata
- Add --skip-scripts, --skip-flows, --skip-apps flags for granular control
- --schema-only now properly skips flows and apps (they only have locks)
- Dynamic "Checking X, Y, Z..." message based on what's being processed
- Show warning when all types are skipped
- Add comprehensive tests for all flags:
- --dry-run shows stale items without updating
- --schema-only only processes scripts
- --skip-scripts, --skip-flows, --skip-apps work correctly
- skipping all types shows warning
- 'All metadata up-to-date' when nothing to update
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* improve output
Signed-off-by: pyranota <pyra@duck.com>
* refactor(cli): add shared test fixtures with cross-links
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(cli): add folder argument to generate-metadata command
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Signed-off-by: pyranota <pyra@duck.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* add a hint to commit message on git sync for renames
* chore: update ee-repo-ref to 344e2aa60d8cafe08b8c57445d2f9555bad7625a
This commit updates the EE repository reference after PR #458 was merged in windmill-ee-private.
Previous ee-repo-ref: ad4c90cc96ad1b4511cba6b6ee28351895882156
New ee-repo-ref: 344e2aa60d8cafe08b8c57445d2f9555bad7625a
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
The windmill-indexer pod was crash-looping due to OOMKilled (exit 137)
with a 2Gi memory limit. Two concurrent tantivy IndexWriters each
allocating 300MB (600MB total), combined with large uncommitted batches
of 50K jobs, caused memory exhaustion during indexing.
- writer_memory_budget: 300MB → 150MB (2 writers = 300MB total)
- commit_job_max_batch_size: 50,000 → 10,000
- commit_log_max_batch_size: 10,000 → 5,000
These can still be overridden via env vars or global settings.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* ci: add freshness check for auto-generated system prompts
Add a CI workflow and script to verify system_prompts/auto-generated/
stays in sync with its source files (SDKs, schemas, CLI commands, etc).
Also remove the hardcoded CLI version from generated output to avoid
unnecessary churn on every release.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* imports
* imports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* regenerate system prompts after rebase on main
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add auto_commit option to Kafka triggers with manual commit API
Add ability to disable auto-commit on Kafka triggers so users can
manually commit offsets after processing messages. This prevents
message loss when processing fails.
Changes:
- Add `auto_commit` column to kafka_trigger table (default true)
- Add POST /kafka_triggers/commit_offsets/{path} endpoint using
BaseConsumer with manual assign() to avoid rebalance
- Enrich trigger_info payload with partition and offset fields
- Conditionally commit based on auto_commit setting
- Add auto-commit toggle to frontend Kafka trigger config
- Add commitKafkaOffsets helpers to Python and TypeScript SDKs
- Add integration tests for auto_commit DB defaults
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: use DB-based pending commits for kafka manual offset commit
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: pass trigger_path to all v2 preprocessors, secure commit_offsets endpoint, fix commit semantics
- Add trigger_path to v2 preprocessor event for all trigger types (kafka, nats, sqs, mqtt, gcp, postgres, websocket, http, email)
- Secure commit_offsets endpoint: infer trigger from job token (OptJobAuthed) instead of requiring trigger path parameter
- Fix auto_commit: only commit offset after successful job push
- Fix pending commits: commit offset+1 (Kafka semantics) and use CommitMode::Sync
- Update TS/Python clients and frontend preprocessor templates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add advanced section badges and reorganize kafka trigger settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove dead wm_trigger assertions from kafka e2e test
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* sqlx
* refactor: remove unused advancedCollapsed state from all trigger editors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update ref
* chore: update ee-repo-ref to ed2c9d360e6fab866b9744cc79f50038d1fc7152
This commit updates the EE repository reference after PR #452 was merged in windmill-ee-private.
Previous ee-repo-ref: 5b31116a1d5a042c6a780732901cfd89584d1773
New ee-repo-ref: ed2c9d360e6fab866b9744cc79f50038d1fc7152
Automated by sync-ee-ref workflow.
* fix: use path-based auth for kafka commit_offsets endpoint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to fcd3ea52b0cc94fbe1159baf662a38da947456de
This commit updates the EE repository reference after PR #457 was merged in windmill-ee-private.
Previous ee-repo-ref: b3a5c33c92cb1b2caf7a65986d71da291ff72a35
New ee-repo-ref: fcd3ea52b0cc94fbe1159baf662a38da947456de
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <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>
* test: add E2E git sync integration tests with Gitea
Add 7 end-to-end tests that verify the full git sync pipeline:
deploy objects in Windmill → DeploymentCallback job runs hub sync script →
correct files appear in a Gitea git repository.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: auto-manage git sync script version locked to Windmill release
- Add LATEST_GIT_SYNC_SCRIPT_PATH constant as single source of truth
- Backend auto-fills empty script_path with latest on save
- New repos use empty script_path (auto-managed by backend)
- Existing repos with pinned versions show warning with opt-in button
- cache_hub_scripts always caches the latest constant
- Rename hubPaths.json gitSync entries to deprecated_ prefix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref.txt for git-sync-tests branch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove upgrade_git_sync_script_paths from save path
Empty script_path is now resolved to latest at job dispatch time in EE,
not on save. Users opt in via the UI button.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: use Option<String> for git sync script_path
None means auto-managed (uses LATEST_GIT_SYNC_SCRIPT_PATH),
Some(path) means pinned to a specific script. Resolution happens
at job dispatch time via effective_script_path().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: separate git sync tests into dedicated CI workflow
- Remove git_sync_test from default integration test suite
- Move gitea service to dedicated docker-compose.git-sync.yml
- Add run_git_sync.sh script
- New workflow triggers on changes to git sync crate, hub paths,
ee-repo-ref, or the test files themselves
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add Rust integration tests for git sync filtering logic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: run git sync E2E tests via cargo run instead of docker image
Build from source and run Windmill directly, start Gitea as a
standalone container. Tests run against localhost — no pre-built
Docker image needed, works on PRs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add relevance check before running git sync E2E tests
Only run the expensive build+test when actually relevant:
- Direct git sync file changes: always run
- ee-repo-ref.txt changed: check if EE diff touches windmill-git-sync/
- Unrelated changes to workspaces.rs or other files: skip
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove broad path triggers from git sync workflow
Remove workspaces.rs and wmill_integration_test_utils.py from path
triggers - they change too often for unrelated reasons. Keep only
git-sync-specific paths + ee-repo-ref.txt (filtered by check-relevance).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: rewrite git sync E2E tests with full coverage and fix test infra
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: remove accidentally committed gen files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: remove unit/integration tests for git sync filtering (covered by E2E)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use correct build features and pass license key to test step in CI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add workflow_dispatch trigger to git sync test workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update commit reference in ee-repo-ref.txt
* fix: update stats_oss stubs to match EE telemetry signature changes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: disable -D warnings for git sync e2e build step
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: allow test connection button in auto-managed git sync mode
The test connection button was disabled and runTestJob() bailed out
when script_path was unset. The test job uses a separate hub script
(gitSyncTest), not the sync script, so the guard was wrong.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to include auto-managed script_path fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use full SHA in ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review feedback
- Initialize current_count before loop in wait_for_sync_jobs
- Clean up temp directories in clone helpers with addCleanup
- Fail CI startup steps if Gitea/Windmill never become ready
- Assert exact job count in exclude_path test
- Remove docs/git-sync-tests-plan.md (stale planning doc)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove orphaned git_sync.sql fixture
No longer referenced after Rust integration tests were removed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: assert old file removal in rename test
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to 612d96a66f9d0cfdae335ef3eb4881f3444ce7cd
This commit updates the EE repository reference after PR #442 was merged in windmill-ee-private.
Previous ee-repo-ref: a05004a7c82f3d1ee5f6863bb9f5a33827d30032
New ee-repo-ref: 612d96a66f9d0cfdae335ef3eb4881f3444ce7cd
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Resources like ansible_inventory have content but no language field,
causing the DiffEditor setupModel guard to skip initialization entirely.
Fall back to 'plaintext' when no language is provided but content exists.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Native mode (from DB config) was not accounted for when sizing the
connection pool or setting SLEEP_QUEUE, because both read NUM_WORKERS
from env which is never set when native mode is configured via the
worker group config in the database.
- Resolve native mode early (before connect_db) by querying the config
table with the initial DB connection
- Pass num_workers directly to connect_db instead of re-reading env
- Replace SLEEP_QUEUE lazy_static with sleep_queue() function that
checks NATIVE_MODE_RESOLVED at runtime (returns 300ms for native)
- Set NATIVE_MODE_RESOLVED immediately when is_native_mode_from_env()
- Allow native_mode in CE worker group config (was silently stripped)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: support minimal telemetry mode for EE
When EE customers disable telemetry, send a reduced payload with only
license-compliance data instead of ignoring the setting. Job usage data
is excluded in minimal mode. The telemetry settings UI now shows in EE
with context-appropriate descriptions for both CE and EE.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref for telemetry-minimal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: make telemetry toggle label and description license-aware
Show "Minimal telemetry" with EE-specific description on EE, and
"Disable telemetry" with CE-specific description on CE.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update commit hash in ee-repo-ref.txt
* Update reference hash in ee-repo-ref.txt
* chore: update ee-repo-ref to 2f52c015bc6c81391234fa87b27ee1d4cd3a48a3
This commit updates the EE repository reference after PR #440 was merged in windmill-ee-private.
Previous ee-repo-ref: 3628ed51426d8d29b3d5c62864ba256b7f9eab17
New ee-repo-ref: 2f52c015bc6c81391234fa87b27ee1d4cd3a48a3
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: skip token expiry notifications for debugger and mcp-oauth tokens
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: update frontend isUserToken to match backend filter
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: add cross-reference comments to token filter functions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: partition audit log table by day with configurable retention
Introduce daily range partitioning for audit logs to replace expensive
DELETE-based retention with instant DROP TABLE per partition.
- Create `audit_partitioned` table alongside existing `audit` table
- New inserts go to `audit_partitioned`, reads UNION ALL both tables
- Monitor creates future partitions and drops expired ones
- Add `audit_log_retention_days` instance setting (default 365 days)
- Old `audit` table empties naturally via existing DELETE cleanup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add audit log retention setting to Core instance settings UI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: bump audit partitioning migration timestamp to avoid collision
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref.txt for audit partitioning
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add RLS/grants to audit_partitioned, run partition mgmt hourly, CE default 14d
- Add grants for windmill_user/windmill_admin and all 5 RLS policies
- Move manage_audit_partitions to hourly via should_run(120)
- Default retention: 14 days CE, 365 days EE
- Download JSON button is now icon-only
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address code review — quote SQL identifiers, add workspace index, deduplicate retention logic
- Quote partition names in dynamic SQL for defense in depth
- Add idx_audit_partitioned_workspace(workspace_id, timestamp DESC) index
- Extract audit_log_retention_days() helper to deduplicate retention logic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref for audit insert error handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to cef4dfc45e6d6344c5d8d107bd2b4d1bf9bbdd64
This commit updates the EE repository reference after PR #450 was merged in windmill-ee-private.
Previous ee-repo-ref: f09284bb257d461bcbe3c50fe31eb6f1e7eafee5
New ee-repo-ref: cef4dfc45e6d6344c5d8d107bd2b4d1bf9bbdd64
Automated by sync-ee-ref workflow.
* fix: create audit partitions on startup in initial_load
Ensures partitions exist before any requests arrive, closing the gap
between server start and the first hourly monitor run.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: always increment zombie job counter to prevent infinite loop at restart limit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: force-complete zombie jobs when handle_job_error fails
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: revert counter increment change, keep re-detection for robustness
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: disable schedule when permissioned_as user is not found
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add preprocessor support for dedicated workers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract transform_and_run helper in python dedicated wrapper
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add preprocessor support for bunnative scripts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: resolve unused postprocessor variable in python wrapper
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: add workflow_dispatch trigger to backend integration tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: bunnative fixture lock format and PrewarmedIsolate::spawn callers
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: update generate_dedicated_worker_wrapper callers in bun_jobs test
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use non-dedicated workers in preprocessor integration tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: prewarm preprocessor isolate for bunnative dedicated workers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: flatten bunnative dedicated worker preprocessing into single result path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use labeled block instead of async block for EE compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update commit reference in ee-repo-ref.txt
* chore: update ee-repo-ref to e36945b987f7904fa984181baf3124e7b2722bd1
This commit updates the EE repository reference after PR #445 was merged in windmill-ee-private.
Previous ee-repo-ref: 8a2625833452aadb8907242bf502b24ca2dffd73
New ee-repo-ref: e36945b987f7904fa984181baf3124e7b2722bd1
Automated by sync-ee-ref workflow.
* Fix merge conflict in ee-repo-ref.txt
Resolve merge conflict in ee-repo-ref.txt
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Fix portal class mismatch in clickOutside that caused premature dropdown
closing, and simplify TeamSelector/ChannelSelector state sync to use
getter/setter bindings instead of bidirectional $effect chains.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: rename AnthropicPlatform to AIPlatform for generic vertex support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Vertex AI support for Google Gemini models
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update platform doc comments to be provider-generic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update googleai default models to latest gemini 2.5 and 3
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move AIPlatform to windmill_common to avoid duplication
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: hugocasa <hugo@casademont.ch>
* fix: preserve teams oauth tenant on settings page reload
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: don't set unused tenant field on microsoft oauth init
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct debounce max_total_debounces_amount semantics and complete previous job on limit exceeded
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: enable debounce arg accumulation for post-preprocessing flows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add debounce accumulation tests for max_count and max_time limits
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add push-time max_count and max_time accumulation tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* sqlx
* sqlx
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Instruct AI to pass specific flow folder path to `wmill flow generate-locks`
instead of running it on all flows. Also add guidance for TypeScript language
files to check `rt.d.ts` for available resource types before using them.
Re-ran generate.py to propagate changes to all auto-generated files.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add git sync support for workspace dependencies
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: implement git sync for workspace dependencies
Signed-off-by: pyranota <pyra@duck.com>
* remove deno.lock
Signed-off-by: pyranota <pyra@duck.com>
* update ee
Signed-off-by: pyranota <pyra@duck.com>
* add tests to cli
Signed-off-by: pyranota <pyra@duck.com>
* sqlx
* chore: update ee-repo-ref to 09dfb247f6f59c61b7f2431932c4557fb26c22d8
This commit updates the EE repository reference after PR #446 was merged in windmill-ee-private.
Previous ee-repo-ref: 8a8832ae5d7efab85b3a57a740308ececa0e2aac
New ee-repo-ref: 09dfb247f6f59c61b7f2431932c4557fb26c22d8
Automated by sync-ee-ref workflow.
* fix test
* fix ci test
Signed-off-by: pyranota <pyra@duck.com>
---------
Signed-off-by: pyranota <pyra@duck.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: delete debounce_key entry when post-preprocessing limits exceeded
For preprocessor flows, the runnable_settings_handle has
debounce_delay_s = None, so maybe_apply_debouncing at pull-time
won't clean up stale debounce_key entries. Previously we only
reset the entry (UPDATE), but since the flow executes immediately
without rescheduling, a stale entry would cause the next incoming
flow to incorrectly try to debounce against an already-executing job.
Change from UPDATE (reset) to DELETE so the entry is fully removed.
Update tests to expect deletion instead of reset.
Companion EE PR: https://github.com/windmill-labs/windmill-ee-private/pull/448
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: persist accumulated debounce args to v2_job for flows
The in-memory arg accumulation in maybe_apply_debouncing was not
persisted back to v2_job. For scripts this is fine (single execution),
but for flows, subsequent steps re-read args from the DB via
get_mini_pulled_job and would see the original (non-accumulated) value.
Also improve the job log message to show both original and accumulated
argument values for clarity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to d1c14355026151ecdd31adda8e2c60ecd1b5ad65
This commit updates the EE repository reference after PR #448 was merged in windmill-ee-private.
Previous ee-repo-ref: bff784002a3335af7c10982599c8f03e536d5abf
New ee-repo-ref: d1c14355026151ecdd31adda8e2c60ecd1b5ad65
Automated by sync-ee-ref workflow.
* test: assert accumulated debounce args are persisted to v2_job
Add DB persistence assertions to accumulation tests to prevent
regressions on the fix that writes accumulated args back to v2_job.
Without this, flow steps re-reading args from the DB would see
the original (non-accumulated) value.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* sqlx
* chore: update ee-repo-ref.txt to ee-private main
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: parallel branchall hang on bad stop_after_all_iters_if + results.x.length null
Two fixes:
1. When a parallel branchall/forloop has a `stop_after_all_iters_if` expression
that fails (e.g. bad JS syntax), the error was propagated with `?`, causing
the transaction to roll back the parallel index increment. Since all parallel
jobs were already completed, nothing could ever increment the index again and
the flow hung forever. Now the error is caught and converted to a stop-early
failure so the transaction commits and the flow fails gracefully.
2. Expressions like `results.a.length` in step input transforms resolved to null
because the `handle_full_regex` fast path intercepted them and used
PostgreSQL's `#>` JSON path operator, which can't resolve JS runtime
properties like `.length` on arrays. Now the fast path skips expressions
ending with JS-only properties (like `length`), falling through to full
QuickJS evaluation where they work correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add kafka trigger offset reset and auto.offset.reset configuration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref for kafka offset reset
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref for subscribe+seek approach
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref for kafka offset reset fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use ConfirmationModal instead of browser confirm() for kafka offset reset
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref for offset commit fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* sqlx update
* Update ee-repo-ref.txt
* update ee ref
* update sqlx
* update ee ref
* chore: update ee-repo-ref to a70d7db187aa78a7fbfd3bfaf92372160cff320a
This commit updates the EE repository reference after PR #444 was merged in windmill-ee-private.
Previous ee-repo-ref: 238c2c0a91f353126f349a5153173a6d16c9d652
New ee-repo-ref: a70d7db187aa78a7fbfd3bfaf92372160cff320a
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
On backend startup, verify the custom_instance_user can connect to the
database with the stored password. If the connection fails, automatically
refresh the password by calling refresh_custom_instance_user_pwd_inner().
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(frontend): replace flat sugiyama with recursive compound layout for flow graph
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(frontend): double forloop wrapper padding and include wrappers in bbox
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(frontend): gate debug wrappers behind SHOW_DEBUG_WRAPPERS flag
Remove all debug console.log calls from compoundLayout and gate
WrapperInfo creation and wrapper node rendering behind an exported
SHOW_DEBUG_WRAPPERS constant. Replace wrapper-based bbox computation
with groupLayouts-based loop so no WrapperInfo is needed for correct
layout. Add contentMinX to LayoutResult for the top-level minX shift.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(frontend): remove debug wrapper nodes from flow graph
Remove WrapperInfo type, SHOW_DEBUG_WRAPPERS flag, buildDebugWrapperNodes
helper, DebugWrapperNode component, and all related plumbing in
FlowGraphV2. The bbox computation now uses groupLayouts directly,
keeping layout correctness without any debug wrapper overhead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* perf(frontend): optimize compoundLayout recursive algorithm
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(frontend): remove dead offset plumbing from flow graph
The old flat sugiyama layout used a CSS margin-left hack (offset) to
indent loop bodies. The new recursive compound layout handles indentation
natively via coordinates, making the entire offset pipeline dead code.
Removes offset from 11 node type definitions, NodeLayout, addNode helper,
processModules parameter, NodeWrapper prop, 9 node renderers, AssetNode
x-position calculations, AIToolNode x-position calculations, DragGhost
nodeOffset function, FlowGraphV2 layout pipeline, util.ts type signatures,
noteUtils NodeDep type, and noteEditor function signature.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(frontend): remove unused lastXCenter variable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* perf(frontend): optimize compoundLayout hot paths
Replace O(N²) queue.shift() with index pointer in BFS, eliminate
redundant groupOwnedIds double-build, use Set for parent dedup,
track minY in existing bbox loop, and cache maxBranchHeight.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove debug artifacts from PR
Remove elk_viewer test page, console log dumps, and layout screenshots
that were used during development.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(frontend): guard data.module.value access in ModuleNode
When rapidly clicking expand/collapse on a subflow, the graph rebuilds
and data.module can be transiently undefined. Add optional chaining to
prevent "Cannot read properties of undefined (reading 'value')" errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(frontend): simplify CompoundGroup type to 'branch' | 'loop'
The layout never distinguishes branchall/branchone or forloop/whileloop,
so collapse to two variants that match the actual code paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(frontend): address PR review feedback on flow layout
- Add max recursion depth guard (50) to layoutLevel to prevent stack
overflow with malformed flow data
- Log swallowed decrossOpt error as console.debug for debuggability
- Initialize maxY to -Infinity for correctness with negative positions
- Fix indentation artifacts in graphBuilder data objects
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* formatting
* fix: remove offset field from asset node data in FlowGraphV2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add indexer time window setting (default 7 days)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add time window note to search UIs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: fetch indexer time window from API in search UIs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to 9df755c57fbfc88f4a724e1ea51b1d5f5af4fe52
This commit updates the EE repository reference after PR #447 was merged in windmill-ee-private.
Previous ee-repo-ref: c17f16bf45091272974e3aa8009cdf5cc15669bf
New ee-repo-ref: 9df755c57fbfc88f4a724e1ea51b1d5f5af4fe52
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* perf: optimize job_stats storage for timestamps and zero-memory jobs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update sqlx offline cache nullable metadata
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: use centisecond offsets for job_stats timestamps (~248 day range)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update SELECT to use offsets_cs column name
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: workflow-as-code v2 with @task decorator API
Replace ctx.step("name", "script") API with @task decorators where
functions are called directly. Users no longer need to pass WorkflowCtx
or use string-based step names/script paths.
Python: @task decorator with contextvars-based implicit context
TypeScript: task() wrapper with module-level context variable
Parsers: detect @task function calls instead of ctx.step() calls
Worker: updated wrappers to set implicit context
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: WAC v2 checkpoint/replay with _executing_key child dispatch
- Rust-side orchestration: parent dispatches child jobs, suspends, resumes on completion
- _executing_key in checkpoint tells child which step to execute directly
- task() throws StepSuspend(mode="step_complete") after executing target step
- result_processor handles child completion and updates parent checkpoint
- WacGraph.svelte for runtime execution visualization
- Sequential and parallel workflows tested end-to-end
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: WAC v2 bundle cache, globalThis ctx sharing, description optional
- Disable bun bundle caching for WAC v2 scripts (wrapper needs
windmill-client from node_modules, not available in bundle mode)
- Use Reflect.set/get(globalThis, "__wmill_wf_ctx") to share workflow
context across dual module instances (wrapper vs user script)
- Never-resolving thenable for non-matching steps in child job mode
prevents Promise.all race conditions
- Make description field optional in NewScript API (defaults to "")
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add step() primitive for inline checkpointed steps
step() executes a function inline (no child job) and persists the result
to the checkpoint. On replay, the cached value is returned — ensuring
deterministic behavior for non-deterministic operations like Date.now()
or Math.random().
- TypeScript: step(name, fn) — executes inline, throws StepSuspend with
mode "inline_checkpoint" to persist before continuing
- Rust: InlineCheckpoint variant in WacOutput, saves to checkpoint and
resets running=false for immediate re-pickup (no zombie wait)
- Shared step counter between task() and step() via _allocKey()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Python WAC v2 support with task(), step(), workflow()
- Python SDK: WorkflowCtx with _executing_key child mode, _alloc_key
shared counter, _run_inline_step for step(), _execute_directly and
_never_resolve for child mode, step() async function
- Python executor: WAC v2 detection, checkpoint.json writing, WAC
wrapper.py generation calling _run_workflow(), post-execution hook
into shared handle_wac_v2_output()
- Make handle_wac_v2_output pub so both bun and python executors share
the same dispatch/suspend/inline-checkpoint logic
- 17 Python tests covering dispatch, replay, parallel, conditional,
inline checkpoint, and child mode
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update sqlx prepared queries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: WacGraph Tooltip→Popover, simplify wacToFlow parsers
- Fix type error: Tooltip doesn't accept text snippet, use Popover
- Extract shared helpers for task matching and block collection
- Replace linear tasks.find() with Map lookups
- Remove mutable module-level counter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: Box::pin WAC v2 output handler to prevent stack overflow
handle_python_job's async state machine was too large when combined
with handle_wac_v2_output. Box::pin heap-allocates the future.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: merge WAC v1 and v2 task decorators to preserve backward compat
The v2 @task decorator was shadowing the v1 one, breaking WAC v1
scripts that rely on HTTP-based dispatch via /workflow_as_code/ API.
The merged decorator handles three modes:
- v2: inside @workflow context → checkpoint/replay dispatch
- v1: WM_JOB_ID set, no @workflow → HTTP API dispatch + wait_job
- standalone: no Windmill env → execute function body directly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: skip no_main_func detection for WAC v2 scripts in TS and Python parsers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent empty/noop dispatch causing infinite requeue loop
- Validate steps.len() > 0 in WAC dispatch handler (issue 3)
- Replace noop StepSuspend throw with never-resolving promise so it
can't reach the backend as an empty dispatch (issue 4)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: Python task wrapper now converts positional args to kwargs in v2 mode
Previously only **kwargs were passed to _next_step(), silently dropping
positional arguments. Extract shared _merge_args() helper used by both
v1 and v2 paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: replace unwrap() with proper error propagation in WAC arg serialization
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add workspace_id filter to v2_job queries in WAC dispatch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent race condition in WAC child dispatch
Restructure dispatch to save checkpoint + suspend parent + seed child
checkpoints in a single transaction BEFORE pushing child jobs. This
ensures a fast child can't complete before the parent is suspended.
Also wrap InlineCheckpoint save + running reset in a transaction to
prevent corrupted state on crash.
Use ULID for pre-generated child job IDs (consistent with rest of API).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: include step key and child job ID in WAC error propagation
Move step_key lookup before the success check so failed child errors
include which task failed, the child job ID, and the original error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: document WAC determinism contract and step dispatch semantics
- Document that workflow functions must be deterministic across replays
- Document that WacStepDispatch.script/args are metadata, not dispatch targets
- Add comments on counter-based key allocation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: tighten WAC v2 detection to reduce false positives
Replace naive substring matching with line-aware checks that skip
comments and look for specific patterns:
- TS: import from "windmill-client" containing workflow/task
- Python: @workflow and @task decorators with wmill import
Extracted shared helpers in wac_executor.rs used by both executors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: show failed steps in WacGraph when workflow completes with errors
When flowDone is true and a pending step isn't in completedSteps,
mark it as 'failed' instead of 'running'. The failed state CSS and
XCircle icon were already defined but never triggered.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: unsuspend and fail parent when WAC child push fails
Previously if a child push failed mid-batch, the parent remained
suspended with suspend = num_steps but fewer children, hanging until
the 14-day timeout. Now the push loop catches errors and unsuspends
the parent before returning the error.
Also adds source hash validation: if the script content changes between
replays, the job fails with a clear error instead of silently feeding
stale checkpoint data into wrong steps.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: clear suspend_until when unsuspending WAC parent
Set suspend_until = NULL alongside suspend = 0 in both the child
failure and all-children-complete paths, so the parent doesn't rely
on subtle pull query invariants to be re-picked-up.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add exhaustive edge case tests for WAC v2 SDK
fix: make TS task wrapper non-async to fix unawaited task flush
The async wrapper caused microtask-based thenable auto-resolution that
fired .then() and threw StepSuspend before _flushPending() could capture
unawaited steps — making the flush mechanism completely broken. Now the
thenable is returned directly without async wrapping. Backward compatible
with v1 (all code paths still return awaitables).
Tests added (59 TS + 66 Python) covering: full sequential lifecycle,
step after parallel, parallel after parallel, conditional on step result,
empty/single-task workflows, 10+ steps, falsy value preservation, inline
steps, mixed step/task, unawaited flush, child mode with parallel,
key determinism, large parallel groups, and complex mixed patterns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: atomic checkpoint updates to prevent parallel child race condition
Replace read-modify-write pattern in handle_wac_child_completion with
atomic SQL operations:
- completed_steps merged via jsonb_set(... || jsonb_build_object(...))
so concurrent children on different workers don't overwrite each other
- suspend counter decremented atomically with RETURNING to determine
"all done" condition (instead of checking completed_steps in memory)
- suspend_until cleared in the same atomic decrement statement
Before this fix, two parallel children completing simultaneously could
both load the same checkpoint, each add their step, and save — the
second write would overwrite the first, silently losing a child result
and leaving the parent suspended forever.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: cancel already-pushed children on partial WAC dispatch failure
When pushing child jobs sequentially, if pushing child N fails, children
1..N-1 are already running. Previously the error handler only unsuspended
the parent, leaving orphaned children that would complete and corrupt the
checkpoint state (decrementing suspend on an already-unsuspended parent,
potentially causing duplicate step execution on re-run).
Now on partial failure:
1. Cancel all already-pushed children (prevents them from completing
and corrupting checkpoint state)
2. Clear pending_steps from checkpoint (so parent doesn't think
children are outstanding on re-run)
3. Then unsuspend parent (so the error propagates)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: skip WAC duration write and child check for non-WAC parents
The duration write to workflow_as_code_status was running for every
non-flow child with a parent (error handlers, success handlers,
run_script children), even though it was only intended for WAC jobs.
Add WHERE workflow_as_code_status IS NOT NULL to skip non-WAC parents
entirely. Piggyback RETURNING pending_steps.job_ids on the same query
so WAC v2 child completion needs zero extra DB round-trips on the
success path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: seed child checkpoint in same transaction as push
The child checkpoint insert was happening before the child job was
pushed, violating the FK constraint on v2_job_status. Move it into
the push transaction so the job row exists and the child can't be
picked up before its checkpoint is ready.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: set running=false when WAC parent suspends for child dispatch
The parent job kept running=true after suspending, so workers wouldn't
pick it up when children completed and suspend reached 0. The parent
only advanced when the zombie job detector reset it (~90s). Now the
dispatch suspend sets running=false so the parent is immediately
eligible for pickup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: WAC parent suspend/unsuspend lifecycle
Keep running=true when suspending the parent so the normal pull query
(WHERE running=false) never picks it up. Keep suspend_until non-null
when decrementing suspend to 0 so the suspended pull query
(WHERE suspend_until IS NOT NULL AND suspend<=0) picks it up.
Previously: setting running=false caused infinite restart loops because
the normal pull query has no suspend check and would immediately re-pick
the parent. Clearing suspend_until on the last child prevented the
suspended pull from ever seeing it, requiring the 90s zombie detector.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add approval primitive, flow child completion, timeline fixes for WAC v2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add error propagation, task options, sleep, and parallel for WAC v2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: fix python SDK tests to use name-based keys and add new test coverage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address WAC v2 review findings (sleep timing, error marker, atomicity)
- Fix sleep using suspend=1 instead of 0 to enforce actual delay
- Add approval/sleep resume injection to Python executor
- Fix TS SDK concurrency_limit mapping (was reading wrong property)
- Namespace error marker as __wmill_error to avoid user data collision
- Wrap child completion SQL in transaction for atomicity
- Decrement suspend even when step key is missing (prevents hang)
- Expand TASK_RE to handle export const, let, var, generics
- Validate step key uniqueness before dispatch
- Log warning on checkpoint deserialization failure
- Remove unimplemented delete_after_use from SDKs
- Add TaskError exception class to Python SDK with diagnostic context
- Fix extra positional args handling and add functools.wraps
- Improve getParamNames to handle typed/destructured params
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* sqlx
* sqlx
* test: add WAC v1 e2e integration tests for TS and Python
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: revert fake test versions in typescript-client
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove unused WacGraph component and strip wacToFlow to isWorkflowAsCode
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract shared approval/sleep resume logic into wac_executor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract google ai logic to windmill-common and use native gemini api in chat proxy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: use x-goog-api-key header for google ai non-chat requests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: transform gemini models response to openai format and use correct auth header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: skip thought parts from gemini thinking models in sse stream
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert "fix: skip thought parts from gemini thinking models in sse stream"
This reverts commit dfa01d282c.
* fix: handle tool calls and sanitize schemas in gemini chat proxy
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: move Gemini→OpenAI response conversion to windmill-common
Extract streaming and non-streaming Gemini response conversion into
shared functions in ai_google so the API proxy and worker use the same
logic instead of duplicating format translation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: review fixes for google ai refactor
- Remove duplicate parse_data_url from worker utils, use shared version
from windmill_common::ai_google in both google_ai and anthropic providers
- Improve error diagnostics in google.rs by including HTTP status code
in error messages from Gemini API responses
- Change GeminiToolCallEvent::into_extra_content to instance method
to_extra_content using &self
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate worker Gemini message conversion using pre-flight pattern
Replace the worker's `convert_messages_to_gemini` and
`convert_content_to_parts_with_s3` (~130 lines) with the existing
pre-flight pattern: `prepare_messages_for_api` converts S3 objects to
data URLs, then the shared `openai_messages_to_gemini` handles the rest.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: hugocasa <hugo@casademont.ch>
* feat: add secretKeyRef support for package registry and storage credentials
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref for test coverage commit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to 716b350bce1730b302c66ea69df618fa40f2f16b
This commit updates the EE repository reference after PR #443 was merged in windmill-ee-private.
Previous ee-repo-ref: d8498f003af407853eb1e98673d86d1816dbfeae
New ee-repo-ref: 716b350bce1730b302c66ea69df618fa40f2f16b
Automated by sync-ee-ref workflow.
* fix: box::pin database executor futures to prevent stack overflow
The if-else chain for database languages (postgresql, mysql, bigquery,
snowflake, mssql, oracledb, duckdb, graphql, nativets) was awaiting
futures directly on the stack. With all features enabled, the combined
async state machine became too large for the default thread stack size,
causing stack overflow in test_workflow_as_code.
The match block for main languages already used Box::pin; this applies
the same pattern to the database language branches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Two fixes:
1. When a parallel branchall/forloop has a `stop_after_all_iters_if` expression
that fails (e.g. bad JS syntax), the error was propagated with `?`, causing
the transaction to roll back the parallel index increment. Since all parallel
jobs were already completed, nothing could ever increment the index again and
the flow hung forever. Now the error is caught and converted to a stop-early
failure so the transaction commits and the flow fails gracefully.
2. Expressions like `results.a.length` in step input transforms resolved to null
because the `handle_full_regex` fast path intercepted them and used
PostgreSQL's `#>` JSON path operator, which can't resolve JS runtime
properties like `.length` on arrays. Now the fast path skips expressions
ending with JS-only properties (like `length`), falling through to full
QuickJS evaluation where they work correctly.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: gracefully handle uninitialized OTEL tracing proxy port
When OTEL tracing proxy is enabled but the MITM proxy port hasn't been
assigned yet (race condition at startup, or NUM_WORKERS > 1), fall back
to standard proxy envs instead of failing the job with
"OTEL tracing proxy port not initialized".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: log to job logs when OTEL tracing proxy is unavailable
When the OTEL tracing proxy is enabled but the port isn't initialized
(race at startup or NUM_WORKERS > 1), append a warning to the job logs
explaining why HTTP request tracing is unavailable for that job.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When a flow loops over an empty array, the VirtualList component crashes
trying to access index 0 in an empty range. Add a guard to only render
VirtualList when items.length > 0, showing a "No iterations" message
otherwise.
Fixes#8272
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The potentially_stale block iterated over all migrations including
.down.sql reversible migrations. Down migrations share the same version
as their up counterpart but have a different checksum, causing the
DELETE to remove the up migration row on every startup and triggering
re-application of the concurrent index migrations.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add a "Banned Patterns" section documenting that $bindable(default_value)
on props that can be undefined is banned. The correct alternatives are
using $derived(my_prop ?? default_value) or creating a useMyPropState()
helper higher in the component tree.
Closes#8266
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* disable dynamic fields for db studio config
* Fix SQL safe interpolated arg
* Fix db studio not passing AppEditorContext to modal
* Fix db studio modal grid not being able to move/resize components
* Remove $$props.field usage
* Rename slots to ensure no hyphen
* _props
* _trigger
* OnSelectedIteration type correct capitalization
* rename _content
* Remove afterUpdate
* Migrate everything to svelte 5
* array bind
* Fix popover
* type never
* nit fixes
* Fixed many trivial errors
* onClick
* Fix errors
* use let:
* nit typing
* fix: wrap state_referenced_locally vars with untrack()
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add untrack import
* Fix all syntax errors due to untrack migration
* Fix undefined errors
* Fix more undefined errors
* untrack(() => initialOpen)
* svelte-ignore
* Fix state_descriptors_fixed error in Chart.svelte
Use $state.snapshot() to pass plain copies of data/options to Chart.js
instead of $state proxies. Chart.js's listenArrayEvents tries to define
property descriptors on data arrays, which Svelte 5 proxies reject.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* nit typing
* Merge issue
* Fix "path is not set" error in resource picker / editor
* Fix InputTransformForm error when rerunning some flows
* fix npm run check
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: suppress unused variable warnings on windows builds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref.txt to merged commit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: write fallback package.json for codebase mode to fix nsjail ERR_INVALID_PACKAGE_CONFIG
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: add e2e tests for codebase mode with and without nsjail
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Implement remove_s3_file method
Add method to permanently delete a file from S3 bucket.
* Add test for removing S3 file
Added a test case to verify removal of a file from S3.
* Add remove_s3_file function to delete S3 files
Added a function to permanently delete a file from the S3 bucket.
* Rename remove_s3_file to remove_3_object
* Rename remove_3_object to remove_s3_object
* Rename test method and update S3 object handling
* Rename remove_s3_object to delete_s3_object
* Rename test_remove_s3_object to test_delete_s3_object and remove_s3_object to delete_s3_object
* fix: update CLI bun template to match UI template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: simplify CLI bun template, only add mode comments
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: replace _TRUNC with hash-based MCP tool names (50 char limit)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: reduce MCP tool name limit from 50 to 40 chars
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: use path prefix filtering instead of separate DB query for hashed name resolution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: remove long path warning from MCP token creation (hashing handles long names)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: unify tool prefix parsing and fix extract_path_prefix_from_hashed for Hs- names
- Replace `is_hashed_name` + `parse_hashed_name` with unified `parse_tool_prefix`
that returns `(type_str, is_hub, is_hashed)` in one call
- Fix `extract_path_prefix_from_hashed` to dynamically determine prefix length
(3 for `Hs-`, 2 for `S-`/`F-`) instead of hardcoding index 2
- Simplify `reverse_transform` to reuse `parse_tool_prefix`
- Add tests for invalid prefixes and `Hs-` prefix handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: escape LIKE wildcards in MCP hashed name path prefix query
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: respect favorites scope in hashed tool name resolution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: deduplicate MCP tool name resolution and rename get_path_or_id
- Extract `unescape_path` helper in transform.rs to deduplicate the
3-step placeholder unescape logic
- Extract `find_matching_path` helper in runner.rs to deduplicate
script/flow candidate matching via ToolableItem trait
- Remove verbose tracing::info! logs from hashed tool resolution hot path
- Fix doc comment referencing nonexistent `is_hashed_name` function
- Rename `get_path_or_id` to `get_transformed_path` for clarity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update stale doc comments to reflect MAX_PATH_LENGTH=40
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add volume limits info in CE volumes drawer
Show an info alert in the volumes drawer when running in Community
Edition, mentioning the 20 volumes per workspace and 50 MB per file
limits. Update ee-repo-ref for companion EE changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to a61366dd4d9e9b1f98a421aaa6d3f63194615275
This commit updates the EE repository reference after PR #438 was merged in windmill-ee-private.
Previous ee-repo-ref: 05385738e36e81f5bc51d15c0ca60bba30457c21
New ee-repo-ref: a61366dd4d9e9b1f98a421aaa6d3f63194615275
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add token expiration notifications via email, critical alerts, and webhooks
- Monitor loop checks for tokens expiring within 7 days and sends
email notifications to token owners. Tracks notification state via
new `expiry_notified` column on the token table to avoid duplicates.
- When tokens expire and are deleted, owners are also notified.
- Critical alerts (in-app UI) are gated behind a new instance setting
`critical_alerts_on_token_expiry` (off by default); emails are
always sent regardless of the setting.
- Add TokenExpiringSoon and TokenExpired webhook message variants for
workspace webhook integrations.
- Frontend: show expiration badges and a warning banner on the tokens
table for tokens expiring within 30 days.
- Exclude session and ephemeral tokens from all notifications.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: use separate token_expiry_notification table for dedup
- Replace `expiry_notified` column on token table with a dedicated
`token_expiry_notification` table (token, expiration)
- Insert notification row on token creation via shared
`register_token_expiry_notification()` helper
- Delete notification row atomically when sending the notification
- Clean up orphaned rows in `delete_expired_items()`
- No FK constraint to avoid cascade overhead on token deletions
- Add index on expiration column for efficient range queries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: calendar-based expiration badge and move notification cleanup
- Fix daysUntilExpiration to compare calendar dates instead of time diff
- Move notification row cleanup from delete_expired_items to
check_expiring_tokens to keep it off the hot path
- Use simple expiration <= now() index scan instead of NOT EXISTS join
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* ci: add Windows backend integration test workflow
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* ci: temporarily add push trigger for testing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* ci: add --no-fail-fast to run all test binaries
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: Windows path handling for backend integration tests
- WINDMILL_DIR: use std::env::temp_dir() on Windows instead of /tmp/windmill
- HOME_ENV: fall back to USERPROFILE on Windows when HOME is not set
- loader.bun.js: normalize paths to forward slashes for consistent
comparison with Bun's resolver output on Windows
- bun_executor.rs: convert job_dir to forward slashes in JS template
strings to avoid backslash escape issues (\t -> tab, etc.)
- go_executor.rs: fix windows_gopath() double backslash bug (r"\\" -> "\\")
- bash_executor.rs: default to "bash" (in PATH) on Windows instead of /bin/bash
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: improve Windows diagnostics and fix onLoad handler
- Include path in create_directory_async/sync panic messages
- Add WINDMILL_DIR initialization debug output
- Fix loader.bun.js onLoad: use properly escaped regex instead of
returning undefined (Bun requires onLoad to return an object)
- Add env var debug output to CI workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: sanitize Windows-invalid characters in test worker names and fix cargo path
- Replace :: with __ in worker names (colons illegal in Windows dir names)
- Fix HOME_DIR to fall back to USERPROFILE on Windows
- Add PATH fallback for cargo discovery on Windows
- Add debug logging to bun loader for fetch errors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle single colons in worker names, pass MSVC linker env vars, revert bun debug
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use .exe binary name on Windows and normalize bun import URL paths
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use absolute path for rust binary, normalize bun resolve paths
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use .wurl extension instead of .url for bun import resolution on Windows
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use custom namespace for bun plugin to bypass default file resolution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use virtual namespace for bun import resolution to avoid Windows path issues
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: handle Windows 8.3 paths and namespace-prefixed importers in bun loader
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: strip namespace prefix from args.path and handle absolute imports without leading slash in bun loader
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: simplify bun loader and remove redundant cargo path lookups
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use platform-specific cargo binary path with .exe on Windows
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: replace HOME_DIR with HOME_ENV in rust_executor to remove duplication
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: keep original bun loader on linux, use virtual namespace loader only on windows
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: replace hub error toasts with warning alerts and add disable hub setting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: guard hub script cache refresh when hub is disabled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: optionally enable rust-analyzer plugin in worktree settings
When USE_RUST_PLUGIN env var is set, the worktree-env script now includes
the rust-analyzer-lsp plugin in .claude/settings.local.json.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove rust-analyzer plugin from default settings
The rust-analyzer plugin is now opt-in via USE_RUST_PLUGIN env var
in worktree-env, so it no longer needs to be in the shared settings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add WM_CLONE_DB and USE_RUST_PLUGIN to wmdev startup envs
Defaults both to false so they can be toggled per-worktree.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use explicit truthy checks for WM_CLONE_DB and USE_RUST_PLUGIN
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Prevent workspace corruption when re-encryption fails mid-loop by
wrapping the key update and variable re-encryption in a single
transaction. If any step fails, the entire operation rolls back.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add variable and resource types to flow env variables
Flow env variables can now reference workspace variables ($var:path)
and resources ($res:path) that are resolved at runtime. Adds Variable
and Resource type options to the flow env editor with ItemPicker and
ResourcePicker components, and resolves references in both the flow
worker (via transform_json) and the API fallback endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(frontend): use inline DollarSign icon for variable picker
Replace the separate "Pick" button with the standard inline DollarSign
icon overlay that appears on hover, matching the existing ArgInput
pattern. Also add the icon to the string type input for quick variable
linking from any string field.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: simplify flow env var resolution and json_path handling in API
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(frontend): always show flow env variables in property picker
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: update flow_env openapi type to allow any JSON value
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(frontend): remove redundant variable type from env var dropdown
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(frontend): use Label component and fix alert text in flow env vars editor
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(frontend): avoid redundant stringify/parse roundtrip in env type switch
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: address PR review comments for flow env vars
- Deduplicate db_authed in jobs.rs $var/$res resolution
- Add warn logging on variable/resource resolution failures
- Consolidate $effect blocks and remove auto-type-correction effect
- Make linked variable text a clickable link to variable editor
- Add hash-based variable editor opening on variables page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* perf: avoid cloning entire FlowValue to resolve flow_env references
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Combine YAML/JSON import into tabs within a single drawer (YAML default)
and add full-code app import option. Uses sessionStorage to persist import
data across the full page reload required by cross-origin isolation headers
when navigating to /apps_raw/add.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add context menu with delete option to preprocessor nodes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add delete styling and shortcuts to right-click context menu
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add a comment to each scheduled poll template (Python, Deno, Bun, Go)
mentioning that data tables can be used for more complex states, with
a link to the documentation.
Closes#8220
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add context menu, multi-select actions, and keyboard shortcuts to flow editor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review feedback on context menu PR
- Revert accidental static import of @scalar/openapi-parser (keep lazy-loaded)
- Restore [data-context-menu] in portalDivs for clickOutside compatibility
- Make noteDisabled reactive ($derived) in ModuleNode
- Use platform-aware shortcut hint (⌫ on Mac, Del on Windows/Linux)
- Optimize resolveSelectedModuleIds with single-pass ancestor map
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address additional review feedback on flow context menu PR
- Use $derived.by instead of $derived for computed bounds in SelectionBoundingBox
- Remove redundant structuredClone wrappers around $state.snapshot
- Add null guard for originalModules/targetModules in move handler
- Add upper-bound guard (n < 10000) to copyId loop
- Fix fragile toggle comparison in moveManager with full array equality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* DB Manager state in URL
* Fix state not saving
* shorted uri params
* infer db_type from prefix
* Revert "infer db_type from prefix"
This reverts commit 7415fbed3d.
* dbm syntax
* infer database type
* Omit main and public
* remove legacy #dbmanager:
* Preserve hash
* nit
* Fix remaining dbManagerDrawer objects
* fix: auto-heal corrupted python runtime cache on remote workers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert "fix: auto-heal corrupted python runtime cache on remote workers"
This reverts commit 0ea013a554.
* feat: make WINDMILL_DIR configurable via environment variable
Allow users to configure the base directory for Windmill's tmp/cache files
via the WINDMILL_DIR env var (default: /tmp/windmill). This fixes Python
runtime cache corruption on RHEL systems where systemd-tmpfiles-clean
removes files from /tmp.
Converts TMP_DIR (renamed to WINDMILL_DIR) and all derived cache directory
constants from compile-time const &str (concatcp!) to runtime lazy_static
String values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: deref ERROR_DIR lazy_static for AsRef<Path> and Display traits
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee ref to branch name for CI compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: deref lazy_static constants in all executor files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: panic if WINDMILL_DIR has trailing slash
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: also reject trailing backslash in WINDMILL_DIR for Windows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: deref GO_BIN_CACHE_DIR in test utils
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: replace remaining hardcoded /tmp/windmill paths and validate empty WINDMILL_DIR
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: nsjail powershell mount dst, Windows path assumptions, pwsh deref consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore Windows /tmp path translation in go and bun executors
The Windows path translation replaces /tmp with the Windows temp dir
(e.g. C:\tmp) before normalizing slashes. Without this, the default
WINDMILL_DIR=/tmp/windmill produces paths without a drive letter on
Windows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to 6fd5a2ce908235a17975ad4dbdf0051cd89334f3
This commit updates the EE repository reference after PR #436 was merged in windmill-ee-private.
Previous ee-repo-ref: e8c03e16720833230ebd1878b4c63642ecc6c80f
New ee-repo-ref: 6fd5a2ce908235a17975ad4dbdf0051cd89334f3
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat(frontend): add script recorder for offline replay of script test executions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(frontend): use Video icon for recording instead of Circle
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(frontend): use Disc icon for recording
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(frontend): improve script recorder replay and recording privacy
- Record schema at capture time in ScriptRecording (lockfile unavailable for previews)
- Read schema from recording instead of job object in replay view
- Remove lockfile tab (not available via normal job API for preview jobs)
- Use text-xs for code/schema views, remove max-height limits
- Disable log download button in replay (endpoint won't work without real job)
- Truncate UUIDs in downloaded recordings (last 8 chars) for privacy
- Make activeReplay a $state so $derived(isReplay) in FlowStatusViewerInner
updates reactively, preventing stale reads that caused API calls during replay
- Use JSON round-trip instead of structuredClone to unwrap $state proxies
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: move index management endpoints out of /srch/, add storage size reporting
- Mount management_service() at /api/indexer (authenticated)
- Add management_service() OSS stub in indexer_oss.rs
- Update OpenAPI: /indexer/delete/{idx_name} and /indexer/storage
- Show disk + S3 storage sizes in IndexerMemorySettings UI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add index storage section with refresh button
Move storage sizes into a dedicated "Index storage" section with a
refresh button to reload sizes after clearing an index.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add indexer status endpoint with liveness detection and improve settings UI
Add GET /indexer/status endpoint that combines lock-based liveness
detection with storage sizes. Frontend now shows running/stopped
indicators with last-active timestamps for each indexer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update ee ref
* fix
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The slowStreamIntervalId (which fires "Loading is taking a long time..."
toasts every 15s) was not cleared in onDestroy, causing it to keep
firing after navigating away from the runs page.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When an OAuth provider entry in instance settings has unexpected types
(e.g. `"true"` instead of `true` for req_body_auth), the entire
/api/settings/instance_config endpoint would fail with a deserialization
error, preventing access to any instance settings.
Introduce OAuthClientEntry enum that tries typed OAuthClient
deserialization first and falls back to raw JSON, logging the
deserialization error. This allows the settings page to load even when
individual OAuth entries are malformed.
Also show a user-visible error toast in SaveButton on save failure
instead of only logging to console.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: skip stop_after_if evaluation for skipped (identity) flow steps
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: relax is_identity_job guard to only require skip_if
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use exact matching for python requirements directive parsing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: apply same exact matching fix to CLI parser
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add right-click context menu to ObjectViewer
Add a contextual menu to ObjectViewer.svelte that appears on right-click
with three actions:
- Copy value: copies the field's value to clipboard
- Copy object key: copies the property key name
- Copy entire object: copies the parent object as JSON
Uses setContext/getContext to share the context menu handler across
recursive ObjectViewer instances, rendering a single menu at the root
level via Portal. Reuses existing contextMenuStyles for visual consistency.
Closes#8177
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* Fix popover closing
* Use existing ContextMenuItem patterns
* hover style
* close contextmenu on pointerdown outside
* try catch for circular objects
* Fix copying undefined not working
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Diego Imbert <diego@windmill.dev>
Co-authored-by: Diego Imbert <70353967+diegoimbert@users.noreply.github.com>
* chore(workmux): add name field to config
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update .workmux.yaml
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: preserve debouncing settings for post-preprocessing arg accumulation
After preprocessing completes, store the flow's debouncing settings in
runnable_settings_handle on v2_job_queue so that maybe_apply_debouncing
can find them when the surviving job is pulled. Without this, the handle
is NULL and arg accumulation silently does nothing for flows with
preprocessors.
Also adds a debouncing badge in flow settings and 4 focused accumulation
tests covering scripts, flows without preprocessor, flows with
preprocessor (with and without the fix).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update sqlx prepared query for worker_flow.rs change
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(frontend): preserve keycloak realm url between instance settings saves
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(backend): preserve provider-specific oauth fields through round-trip
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* docs: move autonomous-mode reference from CLAUDE.md to system prompt
Remove the autonomous-mode.md bullet from CLAUDE.md and instead reference
it via the workmux system prompt, matching the workmux-web pattern. Also
remove the duplicated "Dev Environment (tmux)" section from
autonomous-mode.md since that info is already in the system prompt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add autonomous-mode.md reference to wmdev sandbox system prompt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add drag-and-drop node movement in flow editor
Replace the 2-step click-based move with drag-and-drop: grab a node's
Move icon, drag it near an insert point, see a visual drop indicator,
and drop to move. Click-based move is preserved as fallback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: hide insert buttons on edges during drag-and-drop
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: unify drop zone and legacy move target styles
Use consistent dot indicator for both drag-and-drop and click-based
move targets. Use text-accent theming, hide insert buttons during drag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: render real SvelteFlow graph in drag ghost for subflows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: center drag ghost on the dragged node instead of the whole subflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: pass isSubflow prop through drag system and improve move UX
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: fade entire subflow during legacy move and drag-and-drop
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* style: use text-secondary for move and drop target indicators
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: improve drag-and-drop visual feedback with proximity cues
Ghost opacity reacts to drop zone proximity (dims when far, brightens
when near). Add move icon badge near cursor that highlights on valid
drop target. Switch hit detection from circular radius to axis-aligned
bounding box matching the node gap dimensions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify DragGhost to always use MiniFlowGraph
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: scale drag ghost using flow viewport zoom instead of fixed width
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: register drop zone positions from BaseEdge instead of recomputing from node data
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: hide node UI clutter during drag and polish drag ghost
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: fade all deeply nested nodes when dragging a subflow
Previously only immediate children of a dragged subflow would fade —
deeply nested nodes (e.g. steps inside a forloop inside a branchall)
stayed at full opacity. Store the full set of dragged node IDs on
DragManager and check set membership instead of single-parent comparison.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: rename DragManager to MoveManager and eliminate moving prop drilling
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: unify subflow node computation for both move modes
Extract getSubflowNodeIds() to moveManager.svelte.ts and populate
draggedNodeIds via a single $effect in DragCoordinator for both legacy
click-to-move and drag-and-drop. Consumers (MapItem, NodeWrapper) now
only check draggedNodeIds set membership instead of dual-checking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: clean up drag-and-drop code review issues
Fix toggle risk in DragCoordinator by using forceSetMoving instead of
the toggle-based setMoving. Remove dead code (DragInfo unused fields,
parentSubflowId, GHOST_ZOOM_FACTOR, debug log), extract duplicated
expressions to $derived variables, and add missing type annotations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: clear click-to-move when drag starts to prevent dual mode activation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: centralize draggedNodeIds cleanup in $effect
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: adjust insertion index when moving node forward in same array
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: address PR review feedback for node move feature
- Snapshot drag ghost once at drag start using untrack() to avoid
recomputing on every nodes/edges change during drag
- Rename setMoving/forceSetMoving to toggleMoving/setMoving for clarity
- Add capture: true to DragCoordinator's Escape handler for consistency
- Rename MOVE_BTN_OFFSET to DRAG_HANDLE_OFFSET with descriptive comment
- Move misplaced import to top of moveManager.svelte.ts
- Replace (n.data as any).offset with typed nodeOffset() helper
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: register asset/AI node types in MiniFlowGraph for drag ghost
MiniFlowGraph was missing asset, assetsOverflowed, aiTool, and
newAiTool node types, so these nodes rendered as invisible elements
that inflated the drag ghost bounding box. Register them so the
ghost renders all node types correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve relative positions to absolute for xyflow child nodes in drag ghost
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use initialViewport instead of fitView so drag ghost matches flow zoom
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* style: format BaseEdge.svelte
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: fade asset and AI tool nodes when their parent is being moved
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: include child nodes of edge-matched nodes in subflow ID collection
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: hide +Tool button when moving nodes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: address PR review feedback (listener cleanup, set iteration, dead code)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: position cancel move button on top of node instead of above it
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: compute draggedNodeIds eagerly via callback instead of reactive effect
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove redundant parentModuleId from NodeWrapper
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: address PR review comments for drag ghost and move manager
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add missing grant_types field to instance config OAuth structs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add missing display_name and tenant fields to instance config OAuthClient
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: use Nix profiles in sandbox Docker image
Replace manual tool installs (rustup, nodesource, curl installers) in
sandbox-image/Dockerfile.sandbox with a single `nix profile install .#sandbox`.
All tools (Rust, Node, Bun, Deno, Go, gh, sqlx-cli, cargo-watch, Chromium,
Playwright, etc.) are now managed declaratively via flake.nix.
- Add `packages.sandbox` and `packages.sandbox-full` buildEnv outputs to flake.nix
- Add `sandbox-env` helper script for browser tooling env vars
- Update playwrightWrapper to export PLAYWRIGHT_BROWSERS_PATH
- Rewrite Dockerfile.sandbox: Nix replaces ~50 lines of manual installs
- Update entrypoint.sh to source Nix profile PATH
- Delete deprecated root Dockerfile.sandbox
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: sandbox image runs as non-root user with wmdev
- Rewrite entrypoint.sh to start PostgreSQL as current user (no
chown/su needed), fixing "Operation not permitted" when wmdev
runs containers with --user
- Add chmod -R 777 /root and passwd entry for UID 1000 so non-root
containers can access bashrc, nix-profile, and tool configs
- Remove apt postgresql server (Nix profile provides it)
- Fix bash history expansion errors from literal `!` in system prompt
- Fix asciinema path reference (available on PATH, not hardcoded)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap pkg-config in sandbox profiles to bake in Nix search path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add openssh-client and sudo to sandbox image for full root access
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use useradd instead of manual passwd entry for sandbox agent user
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The wmill_pg package (psycopg2 wrapper for running PostgreSQL queries)
has been fully replaced by Windmill's native PostgreSQL support.
Remove the package directory and all references from build, publish,
install, version, LSP, and dependabot configs.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Show recurring toast every 15s (8s duration) when loading takes long, with stop button
- When streaming by batches of 25 and a batch takes >4s, offer to stream 1 by 1
- Expose batch size in progress bar with editable input to customize on the fly
- Make stop button more prominent (destructive Button component)
- Fix list_jobs UNION: exclude queue jobs when filtering by result or is_skipped=true
- Add "Show skipped" preset to runs filter
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* perf: batch large job list requests and fix loadExtraJobs cursor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: replace timeout toast with batch progress banner for large job lists
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: show loading indicator on Load more buttons
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: distinguish load-more vs auto-refresh loading indicators
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: offer to stream by batches of 25 when loading is slow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove refreshing text on auto-refresh and clean up unused loading prop
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: batch progress race condition when restreaming with small batches
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The email_domain setting was accidentally removed from the frontend
instance settings in a recent onboarding cleanup. The backend still
fully supports it. This restores the setting in the Core section.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When failures are sparse (<1%), filtering by failure status on the runs
page required scanning millions of success rows. Add a partial index on
v2_job_completed (workspace_id, completed_at DESC) WHERE status IN
('failure', 'canceled') and switch ORDER BY to completed_at when
filtering failures, so Postgres walks the small partial index directly.
Benchmarked at 5.2M rows / 1% failure rate:
- LIMIT 30: 800ms -> 0.4ms (2000x faster)
- LIMIT 1000: 550ms -> 21ms (26x faster)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* perf: lazy-load markdown in Tooltip to reduce stores2 chunk by 335KB
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: migrate TooltipInner to Svelte 5 runes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* perf: remove markdown rendering from Tooltip components
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use HTML tables for date format tooltips to preserve formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update vite to 8 beta, vite-plugin-svelte to 7, vitest to 4.1 beta
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add .npmrc with legacy-peer-deps for vite 8 beta
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: copy deps and remove user auto-add on workspace fork
Clone workspace_dependencies to forked workspaces and remove
automatic workspace_invite creation for parent workspace users.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update sqlx offline cache
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Support arg type decl in postgres
* Python datatable client no longer requires explicit arg typing
* compilation fix
* Set correct type in statement exec
* reset to main
* Explicit pg arg types
* remove code duplication
* update parser js
* FLOAT8 doesn't have space
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Add docsUrl to the Google service config in WorkspaceIntegrations so a
"Docs" button appears next to the Google integration, linking to
https://www.windmill.dev/docs/core_concepts/native_triggers#google-triggers.
This follows the same pattern already used for Nextcloud.
Closes#8090
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Henri Courdent <122811744+hcourdent@users.noreply.github.com>
When connecting Nextcloud via workspace integration OAuth, the resource
now includes baseUrl (from OAuth config) and userId (fetched from
Nextcloud OCS API) alongside the token, making it immediately usable
by scripts. Falls back to token + baseUrl if user info fetch fails.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: slim down claude instructions for lean context and fast iteration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add private and license feature flags to enterprise validation docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add /refine skill for end-of-session doc evolution
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove architecture.md overview doc per research findings
General codebase overviews distract agents and trigger unnecessary
exploration. Keep only operational docs (validation, enterprise).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add autonomous mode doc for bypass permission workflows
Covers: plan-first requirement, tmux pane usage for checking
backend/frontend logs, manual testing via Playwright MCP,
Playwright gotchas, and end-of-task summary expectations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add mermaid, playwright, and asciinema tools to autonomous mode doc
Claude should use mmdc for diagrams during planning, playwright CLI for
screenshots of frontend changes, and asciinema for terminal recordings
of CLI changes. All attached to the PR.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use pastebin for screenshot/recording uploads
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review findings
- Remove stale docs/architecture.md reference from /refine skill
- Fix script name: ./update-sqlx -> ./update_sqlx.sh
- Remove .claude/settings.local.json mention from enterprise doc
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: rewrite flake.nix from scratch for clarity and modularity
Rewrite the Nix flake with clean separation of concerns, organized
let-bindings, and 4 purpose-specific devShells instead of a monolithic
default shell with broken package outputs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add CLI tools to default devShell (gh, aws, playwright, mermaid, asciinema)
Add tools needed for AI agent workflows and dev tooling:
- gh (GitHub CLI)
- awscli2
- asciinema (terminal recording)
- playwright-driver with Nix-managed browsers
- mermaid-cli (diagram generation)
Playwright browsers are provided via nixpkgs' playwright-driver.browsers.
Mermaid/Puppeteer reuses the headless_shell from the same browser set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: move wm-minio scripts to default devShell
MinIO (local S3) is needed for regular development, not just the full
profile.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use playwright wrapper + chromium for browser tools
Replace playwright-driver (library, no CLI) with:
- A `playwright` wrapper script that calls the Nix playwright-core CLI
(version-matched to its own Nix-provided browsers)
- pkgs.chromium for Mermaid/Puppeteer (which respects PUPPETEER_EXECUTABLE_PATH)
This fixes playwright screenshot and mermaid diagram generation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: auto-load .env.local from main worktree in all devShells
Gitignored files like .env.local don't exist in git worktrees.
Add a shared shellHook that resolves back to the main tree via
git-common-dir and sources .env.local if present. This ensures
AWS credentials and other secrets are available in worktrees.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: replace deprecated pkgs.hostPlatform with stdenv.hostPlatform
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove AWS CLI from flake and sandbox images
Pastebin is sufficient for screenshot sharing; AWS credentials
add unnecessary complexity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review — ruby mismatch, quoting, shell dedup
- Fix pkgs.ruby → pkgs.ruby_3_4 in extraRuntimeVars to match extraRuntimes
- Replace $* with "$@" in all helper scripts (wm, wm-build, wm-caddy,
wm-bench, wm-cli) to correctly preserve argument boundaries
- Extract coreBuildInputs, browserVars, and playwrightWrapper as shared
let-bindings to eliminate duplication between default and full shells
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove .env.local auto-loading from devShells
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: replace SELECT * with explicit columns in teams command query
- Update sqlx offline cache for the changed query
- Fix write_latest_ee_ref.sh to prefer matching EE worktree branch
- Update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref to 8ffae1f43b31dc8136714fa612d22b6301773e27
This commit updates the EE repository reference after PR #434 was merged in windmill-ee-private.
Previous ee-repo-ref: da1f8bf8676f85cac2b6fa2705246e1819d4b6f0
New ee-repo-ref: 8ffae1f43b31dc8136714fa612d22b6301773e27
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Store the runs page 'show schedules' and 'show future jobs' filter toggles
in local storage using useLocalStorageValue so they persist across page
navigations. URL parameters remain dominant - local storage values are only
applied when URL params are undefined.
Closes#8123
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
- Remove explicit loadJobs(true) calls from onZoom handlers in RunChart
and ConcurrentJobsChart — setting _timeframe.val already triggers the
effect which calls onParamChanges
- Fix debounce by hoisting promise ref outside effect closure so cleanup
can properly cancel in-flight requests
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: runs page date picker query parameter handling
- Route query params by job status: queue-only statuses (running/suspended/waiting)
use createdBeforeQueue/createdAfterQueue, completed-only statuses (success/failure)
skip queue params, and no filter uses both
- Pass completedAfter (extendedMinTs) on initial load to avoid overfetching
- Skip auto-refresh syncer for manual timeframes (fixed past date ranges)
- Debounce param change effect to prevent double API calls
- Remove redundant timeframe dependency from effect (already tracked via filters)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: only show Load more when jobs count >= perPage
- Default lastFetchWentToEnd to true so Load more is hidden until a
full page is confirmed
- Set lastFetchWentToEnd after initial load, not just after loadExtra
- Add jobs.length >= perPage guard in template to prevent flicker
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add force branch param to git sync settings
* update hub path
* chore: update ee-repo-ref to a797dd4d619cdab737e133ce593f2f8582ba21de
This commit updates the EE repository reference after PR #430 was merged in windmill-ee-private.
Previous ee-repo-ref: 373c5870ae5499c8c8a22cb92b2fd3a64a651183
New ee-repo-ref: a797dd4d619cdab737e133ce593f2f8582ba21de
Automated by sync-ee-ref workflow.
* UI nits
* nit
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
When a flow has a flow-level `skip_expr` (or `no_flow_overlap`) and the first
module has `skip_if` defined, the flow-level condition returns `UpdateFlow`
before any identity job is created. The `UpdateFlow` path passes `Uuid::nil()`
as `job_id_for_status`, causing `fetch_one` to fail with "no rows returned".
- Change `fetch_one` to `fetch_optional` so a missing row returns false
- Short-circuit the DB query with `stop_early && skip_if_stop_early` so both
skip mechanisms (identity job check and early-stop skip flag) are considered
- Also fixes the logical gap where a module with both `skip_if` and
`stop_after_if` would only check the identity job, ignoring the early-stop
skip signal
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Create a dedicated PostgreSQL database for each worktree during
workmux post_create, run sqlx migrations, and drop it on cleanup.
Also auto-trust the worktree directory in ~/.claude.json.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The num_columns i16 was written twice, causing the parser to read
the second copy as column data and misparse the column name.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: optimize slow list_assets query with covering index and v2_job join fix
Add a covering index on asset(workspace_id, path, kind, created_at DESC, id DESC)
with INCLUDE(usage_kind, usage_path) to enable index-only scans for the CTE aggregation.
Fix v2_job join to cast asset.usage_path::uuid instead of job.id::text, allowing
PostgreSQL to use the job_pkey primary key index instead of seq scanning the entire table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: drop redundant asset indexes subsumed by new covering index
idx_asset_workspace_created_id and idx_asset_kind_path are fully covered
by the new idx_asset_ws_path_kind_recent + the primary key. Verified all
asset table queries still have optimal index coverage. Reduces write
amplification on inserts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Exclude `windmill-client` from optimizeDeps (only appears in template
strings, not an actual frontend dependency) and remove uninstalled
`monaco-editor-wrapper` from optimizeDeps.include.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use @-prefixed LIKE pattern for email domain matching in auto-invite
The SQL queries for auto-add and auto-invite used `LIKE CONCAT('%', domain)`
which could match emails from unrelated domains (e.g., `user@barfoo.com`
matching domain `foo.com`). Changed to `LIKE CONCAT('%@', domain)` so only
exact domain suffixes match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update ee-repo-ref.txt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* update: bump Svelte ecosystem to latest Vite 7-compatible versions
Bump svelte (5.39→5.53), @sveltejs/kit (2.49→2.53), vite-plugin-svelte
(6.2.1→6.2.4), svelte-check (4.3→4.4), @sveltejs/package (2.5.4→2.5.7).
Stays on vite-plugin-svelte 6.x to avoid requiring Vite 8.
Fix DucklakeSettings.svelte missing lang="ts" on instance script tag
(new compiler rejects import type syntax in plain JS blocks).
Fix getCurrentModel race condition where changeMode was called reactively
before copilot info loaded, causing "No model selected" error on init.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: move early return guard before mode assignment in changeMode
Avoid inconsistent state where this.mode is set to SCRIPT but
systemMessage/tools/helpers are stale from the previous mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(backend): pass parent_path for trigger renames in git sync
When renaming/moving a trigger path, the old path was not included in
the deployment metadata, so git sync never deleted the old file. This
adds parent_path to all 9 trigger DeployedObject variants and computes
it in update_trigger when the path changes.
Fixes#8014
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix path change with common prefix issue
* update ref
* chore: update ee-repo-ref to cb25312072c15c0e9cc375ebc824d41995a52898
This commit updates the EE repository reference after PR #428 was merged in windmill-ee-private.
Previous ee-repo-ref: 7225f7423311f58015a2fab61248c9d89888aef6
New ee-repo-ref: cb25312072c15c0e9cc375ebc824d41995a52898
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* Update FlowLoop.svelte
Made it clear that only flow level error handlers are triggerd for a failure inside a flow loop with "skip failures" enabled.
* Update FlowWhileLoop.svelte
Add the ability to record a flow test execution and replay it offline
without any API calls. This is useful for debugging, sharing, and
reviewing flow executions outside of a running Windmill instance.
Recording:
- "Test flow & record" option in the flow editor three-dots menu
opens the test drawer in recording mode
- While in recording mode, running a test captures all job events
(SSE streams, sub-job completions, flow status transitions) along
with the flow definition into a downloadable JSON file
- Recording state module (flowRecording.svelte.ts) manages active
recording/replay instances at the module level
Replay:
- Standalone /replay page where users upload a recording JSON file
and watch the flow execute with real-time status transitions
- FlowRecordingReplay component handles timestamp rebasing, event
ordering fixes, and drives FlowStatusViewer with recorded data
- JobLoader intercepts replay mode to feed recorded events via
timed callbacks instead of real SSE/polling
- FlowStatusViewerInner and FlowLogViewer guard all API call sites
to prevent network requests during replay
- Job links, log downloads, and resource lookups are suppressed
in replay mode
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Add CARGO_FEATURES passthrough: backend pane reads from .env.local,
wm-cursor supports --features flag on add/open commands
- Fix node_modules copy in worktrees: use cp -a to preserve .bin/
symlinks that cp -r would dereference (fixes openapi-ts errors)
- Fix EE repo discovery from worktrees: resolve main repo root via
git-common-dir, search multiple candidate paths
- Add cursor session cleanup to worktree-cleanup (pre_remove hook)
- Use workmux -b flag in wmc add, remove npm install from frontend pane
- Change openBrowserOnce for Cursor port forwarding
- Document cargo features usage and fix stale files.symlink reference
in README
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: auto-create missing folders during sync push for non-admin users
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: show missing folders in sync push summary before confirmation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: improve sync push folder auto-creation error handling and json output
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: only treat 404 as missing folder in getFolder check
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: remove obsolete Deno compatibility layer from yaml-validator
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore(cli): add @types/bun dev dependency
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(cli): replace auto-create folders with `wmill folder add-missing` command
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): improve folder commands with summary field and simpler push API
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(cli): add confirmation prompt to folder add-missing command
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(cli): simplify missing folder check to use local stat instead of remote API
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* update skills
* feat(cli): warn admins but block non-admins on missing folder.meta.yaml
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* cleaning
* cleaning
* test(cli): add tests for missing folder detection and folder commands
- Add tests for `folder new`, `folder push`, `folder add-missing` commands
- Add tests for sync push missing folder.meta.yaml detection (admin warning, non-admin block)
- Fix getBasePostgresUrl to strip query params (e.g. ?sslmode=disable) from DATABASE_URL
- Add createNonAdminUser and runCLIWithToken test utilities to test_backend.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(cli): unify runCLICommand with optional token parameter
Replace separate runCLIWithToken utility with an optional { workspace?, token? }
options object on the existing runCLICommand across all backends.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* own workspace
* test(cli): isolate folder_missing_meta tests with per-test workspace
* test(cli): shorten isolated workspace id/name for workspace limits
* test(cli): archive temp isolated workspaces after each folder test
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Set fileset editor container to h-[60vh] so both navbar and editor
fill available space. Match editor filename bar height to navbar header.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add fileset resource type support
Add a new "fileset" resource type that represents a collection of files
stored as a relpath→content map. This enables resource types to manage
multiple files (e.g., config directories, template sets) instead of just
a single file.
Backend:
- Add is_fileset column to resource_type table
- Update CRUD operations and workspace duplication to handle is_fileset
- Add integration tests for fileset resource types
Frontend:
- Add FilesetEditor component with file explorer + Monaco editor
- Extract shared FileExplorer component from RawAppSidebar (dedup)
- Add fileset toggle to EditableSchemaWrapper
- Show fileset editor in ResourceEditor and ApiConnectForm
- Show folder icon for fileset resource types in IconedResourceType
CLI:
- Support fileset resources in sync pull (expand to .fileset/ directory)
- Support fileset resources in sync push (reconstruct from directory)
- Handle !inline_fileset YAML tag in resource resolution
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* sqlx
* fix: resolve svelte warnings and type error in fileset components
- Fix state_referenced_locally warnings in FilesetEditor by computing
initial values before creating $state
- Fix Promise<boolean> type error in +page.svelte by making
resourceNameIsFileset/resourceNameToFileExt synchronous lookups
with eager map loading
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address code review findings for fileset feature
- Use sqlb.set() instead of set_str() for boolean is_fileset field
to avoid quoting (SET is_fileset = TRUE not 'TRUE')
- Add JSDoc comment to isFilesetResource explaining it matches
children inside .fileset/ directories, not the directory itself
- Update OpenAPI spec for file_resource_type_to_file_ext_map endpoint
to document the new response schema with format_extension and
is_fileset fields
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address second round of review findings
- Remove bidirectional $effect sync in RawAppSidebar; bind FileExplorer
directly to files prop with {} default
- Avoid creating new files object on every keystroke in FilesetEditor;
merge editContent → args in a single effect without intermediate spread
- Simplify no-op `?? undefined` in addResourceType
- Add backend validation: reject create_resource_type when both
is_fileset and format_extension are set
- Fix fileset alert title showing undefined format extension
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: exclude app_theme resources from workspace tab
Theme resources (app_theme) were showing on the workspace tab alongside
regular resources. Now they are excluded from the workspace tab
(like cache and state) and the theme tab loads only app_theme resources.
Also includes review fixes:
- Remove bidirectional $effect sync in RawAppSidebar
- Avoid spreading new files object on every keystroke in FilesetEditor
- Simplify ?? undefined no-op
- Add backend validation for is_fileset + format_extension conflict
- Fix fileset alert title
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore full-width file tree items in raw app sidebar
FileExplorer's tree container was missing w-full, causing items to not
stretch inside PanelSection's items-start flex container.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent iframe from overriding file selection after file creation
When files change in the sidebar, setFilesInIframe sends the new files
to the iframe which responds with setActiveDocument defaulting to
App.tsx, overriding the user's selection. Now we ignore setActiveDocument
messages for 500ms after sending setFiles to the iframe.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert "fix: prevent iframe from overriding file selection after file creation"
This reverts commit 7f3ddd7edd.
* fix: suppress iframe setActiveDocument during file population
Use setFilesAndSelectInIframe in populateFiles to keep the current
document selected when re-sending files. Suppress setActiveDocument
for 500ms after population to prevent the iframe from defaulting
back to App.tsx on focus changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Use setFilesAndSelectInIframe in populateFiles to keep the current
document selected when re-sending files. Suppress setActiveDocument
for 500ms after population to prevent the iframe from defaulting
back to App.tsx on focus changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When files change in the sidebar, setFilesInIframe sends the new files
to the iframe which responds with setActiveDocument defaulting to
App.tsx, overriding the user's selection. Now we ignore setActiveDocument
messages for 500ms after sending setFiles to the iframe.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
FileExplorer's tree container was missing w-full, causing items to not
stretch inside PanelSection's items-start flex container.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Theme resources (app_theme) were showing on the workspace tab alongside
regular resources. Now they are excluded from the workspace tab
(like cache and state) and the theme tab loads only app_theme resources.
Also includes review fixes:
- Remove bidirectional $effect sync in RawAppSidebar
- Avoid spreading new files object on every keystroke in FilesetEditor
- Simplify ?? undefined no-op
- Add backend validation for is_fileset + format_extension conflict
- Fix fileset alert title
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove bidirectional $effect sync in RawAppSidebar; bind FileExplorer
directly to files prop with {} default
- Avoid creating new files object on every keystroke in FilesetEditor;
merge editContent → args in a single effect without intermediate spread
- Simplify no-op `?? undefined` in addResourceType
- Add backend validation: reject create_resource_type when both
is_fileset and format_extension are set
- Fix fileset alert title showing undefined format extension
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use sqlb.set() instead of set_str() for boolean is_fileset field
to avoid quoting (SET is_fileset = TRUE not 'TRUE')
- Add JSDoc comment to isFilesetResource explaining it matches
children inside .fileset/ directories, not the directory itself
- Update OpenAPI spec for file_resource_type_to_file_ext_map endpoint
to document the new response schema with format_extension and
is_fileset fields
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix state_referenced_locally warnings in FilesetEditor by computing
initial values before creating $state
- Fix Promise<boolean> type error in +page.svelte by making
resourceNameIsFileset/resourceNameToFileExt synchronous lookups
with eager map loading
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a new "fileset" resource type that represents a collection of files
stored as a relpath→content map. This enables resource types to manage
multiple files (e.g., config directories, template sets) instead of just
a single file.
Backend:
- Add is_fileset column to resource_type table
- Update CRUD operations and workspace duplication to handle is_fileset
- Add integration tests for fileset resource types
Frontend:
- Add FilesetEditor component with file explorer + Monaco editor
- Extract shared FileExplorer component from RawAppSidebar (dedup)
- Add fileset toggle to EditableSchemaWrapper
- Show fileset editor in ResourceEditor and ApiConnectForm
- Show folder icon for fileset resource types in IconedResourceType
CLI:
- Support fileset resources in sync pull (expand to .fileset/ directory)
- Support fileset resources in sync push (reconstruct from directory)
- Handle !inline_fileset YAML tag in resource resolution
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: truncate long summary and path in SummaryPathDisplay
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: allow summary to shrink in flow editor top bar
Add min-w-0 to the SummaryPathDisplay wrapper in FlowBuilder so flex
shrinking works. Remove max-w cap on the wrapper so the summary can use
all available space. Remove w-full and max-w-md from the empty middle
schedule div that was stealing space even when empty.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* worker: support AIAgent tools in AI executor
* worker: complete nested AIAgent tool execution path
* worker: inline AIAgent tool schema usage
* fix agent action
* frontend: add AI Agent as tool type in flow builder
Add the ability to insert a nested AI Agent as a tool within another
AI Agent step. Includes type definitions, factory function, graph icon,
insert/event wiring, and a dedicated editor component.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove AiAgentToolEditor, reuse FlowModuleComponent for AI agent tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: populate all input transforms for nested AI agent tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: avoid missing v2_job_status error for nested AI agent tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* sqlx
* nit
* refactor: cleanup nested AI agent tool implementation
- Add max nesting depth guard (5) on parent chain traversal
- Reject 3+ level nesting explicitly with clear error message
- Remove unnecessary flow_step_id tuple scaffolding in tool dispatch
- Consolidate get_value() calls using borrow in first match
- Replace unsafe `as unknown as FlowModule` casts with agentToolToFlowModule()
- Simplify toolKind ternary chain with .includes() lookup
- Fix leftover over-indentation from tuple removal
- Remove duplicate doc comment on is_completed_input_transform
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: pass flow_step_id and flow_job_id overrides to run_agent for nested AI agents
For nested AI agent tools, job.flow_step_id is None and job.parent_job
points to the parent agent instead of the flow. This caused memory
read/write and flow context resolution to silently fail.
handle_ai_agent_job already computes the correct flow_step_id (via
runnable_path fallback) and flow_job_id (via parent chain traversal).
This change threads those values through run_agent and
ToolExecutionContext so all downstream consumers use the correct IDs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* cleaning
* cleaning
* move const
* fix
* refactor: replace defaultToAi boolean with allowedAiTransforms whitelist
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: propagate root_job at push time, remove flow_job_id_override
Instead of threading flow_job_id_override through run_agent and
get_flow_context, propagate root_job and flow_innermost_root_job
when pushing tool jobs so nested AI agents can find the flow
job naturally via the existing job fields.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: simplify nested AI agent parent chain walk-up
Replace the generic depth-limited loop with a single-level check since
only flow → agent → nested agent tool is supported. Remove
MAX_AGENT_NESTING_DEPTH constant and flatten the module lookup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: reject 3+ level nested AI agent tools before job creation
Check at the parent agent level whether a nested AIAgent tool contains
AIAgent sub-tools. If so, return a fatal error immediately, preventing
the sub-job from being created and avoiding retry loops.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve deadlock in nested AI agent tool execution
Replace channel forwarding with inline DB writes for tool job
completions. Nested agents used bounded(1) channels where a sub-tool's
forwarded result would fill the parent channel, leaving no room for the
agent's own completion — causing a deadlock. Writing directly via
add_completed_job/add_completed_job_error bypasses the channel entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add light mode support for navigation sidebar
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: use custom selected background for sidebar items
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: darken sidebar section separators in light mode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: lighten sidebar bg to #F3F3F7 and use border-light for separators
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use text-secondary for sidebar labels and text-hint for icons
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: extract dark sidebar background color to constant
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add wm-cursor (wmc) script that bridges workmux with Cursor SSH remote,
giving each worktree its own Cursor window with an independently-focused
grouped tmux session.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
description:Checklist for adding a new TriggerCrud-based trigger type to Windmill (Azure, GCP, Kafka, etc.). Use when wiring a new trigger kind across backend, frontend, CLI, and capture infrastructure.
---
# Skill: Adding a New Trigger Type
Use this skill when adding a trigger kind that implements `TriggerCrud` (Kafka, GCP, Azure, MQTT, SQS, NATS, Postgres, Email…). For native triggers (Nextcloud, Google Drive — things wired through `windmill-native-triggers`), use the `native-trigger` skill instead.
The goal of this doc is to enumerate every file that needs to change. Missing any one of them leads to silent regressions: sync drops the trigger, capture button does nothing, workspace forks lose it, sidebar counters undercount. Follow the checklist top-to-bottom — each section is independent enough to be validated on its own.
Throughout this doc, substitute `{kind}` for the new trigger kind (`azure`, `kafka`, …), `{Kind}` for PascalCase (`Azure`, `Kafka`), `{KIND}` for SCREAMING (`AZURE`, `KAFKA`).
## Reference implementations
- **GCP** — closest analogue to Azure. Has push + pull, OIDC auth, ARM-like resource paths, capture handler. Grep for `gcp_trigger` / `GcpTrigger`.
- **Kafka** — simpler (pull-only, streaming). Good for trivial integrations.
- **Azure** — most recently added (2026). Shared-secret push auth, Event Grid namespaces + basic topics, ARM resource discovery, Namespace-pull data-plane. Grep for `azure_trigger` / `AzureTrigger`.
## 1. Database migration
Create a migration: `cargo sqlx migrate add -r add_{kind}_trigger` from `backend/`. Never write timestamps manually.
The `up.sql` usually defines:
- An optional enum type (e.g. `AZURE_MODE`) if the trigger has sub-kinds
- The `{kind}_trigger` table with at minimum these columns (mirrored from kafka/gcp):
- Add `{kind}_used: boolean` to the `UsedTriggers` response schema
Regenerate frontend client: `npm run generate-backend-client` from `frontend/`.
## 6. `UsedTriggers` + workspace export
**`backend/windmill-api-workspaces/src/workspaces.rs`** — add `{kind}_used: bool` to the `UsedTriggers` struct and add an `EXISTS(SELECT 1 FROM {kind}_trigger …)` to the `get_used_triggers` query.
**`backend/windmill-api/src/workspaces_export.rs`** — add export block mirroring gcp's (export lists all triggers, serializes them to YAML/JSON). The block re-uses the `trigger_ignore_keys` variable so the new kind automatically participates in fork-export stripping (`mode` field is omitted when the source workspace is a fork — keeps fork→parent merges from flipping the parent's enabled state).
**Fork cloning (`clone_triggers_and_schedules` in workspaces.rs)** — add an `INSERT INTO {kind}_trigger ... SELECT ...` block that copies all rows from the parent workspace, forcing `mode = 'disabled'::TRIGGER_MODE`. Always runs at fork creation; forgetting this means users can't carry `{kind}` triggers into their forks.
Several files keep **hardcoded arrays** of trigger kind strings. Miss one and ACL checks / user offboarding / trash drop your kind:
- **`backend/windmill-api-groups/src/granular_acls.rs`** — `KINDS: [&str; N]`. **Increment N** (the compile error is cryptic otherwise). Controls which kinds accept granular ACL operations.
- **`backend/windmill-api-users/src/users.rs`** (`extra_perms_tables`) — which tables get `extra_perms` entries cleaned when a user is deleted.
- **`backend/windmill-api/src/offboarding.rs`** — three separate arrays (enumeration, fork-copy, and delete paths). **All three** need the new kind.
- **`backend/windmill-api/src/trash.rs`** — `valid_tables` for the trash / restore API.
- **`backend/windmill-git-sync/src/lib.rs`** — add a test assertion for `DeployedObject::{Kind}Trigger.get_kind() == "{kind}_trigger"` (the `get_kind` match arm itself lives in the enum impl — already required by the Rust compiler).
- **`backend/windmill-api-auth/src/scopes.rs`** — add the `{Kind}Triggers` variant to `ScopeDomain` enum + `as_str` match + `from_str` match. Required for the OAuth/token system to recognise `{kind}_triggers:read|write` scopes.
- **`backend/windmill-api/src/token.rs`** (`build_trigger_scope_domains` → `TRIGGER_DOMAINS`) — add `("{kind}_triggers", "{Kind display name}")` so the CreateToken UI's scope selector surfaces the `read` / `write` checkboxes.
**OpenAPI enums** to extend (do NOT forget — generated client will allow it but server rejects as 400):
-`CaptureTriggerKind` enum
- Three `kind` enums under `/w/{workspace}/acls/{get,add,remove}/{kind}/{path}` (yes, same list repeated three times)
After editing any of these, run a full `cargo check` with your feature flag + `gcp_trigger` + other core flags — the `KINDS: [&str; N]` length mismatch only surfaces when the crate compiles.
If the trigger supports push delivery, it also needs a capture endpoint so users can test it:
-`{Kind}TriggerConfig` struct (gated by feature flags)
-`TriggerConfig::{Kind}` variant
-`set_{kind}_trigger_config` function (creates the subscription/equivalent pointing at the capture URL — use your `manage_{kind}_subscription` helper with `trigger_mode=false`)
- Both real + no-op versions behind feature gates
-`TriggerKind::{Kind} => set_{kind}_trigger_config(...)` arm in `set_config`
- Route: `.route("/{kind}/{runnable_kind}/{*path}", post({kind}_payload))` inside `workspaced_unauthed_service` — and expand the surrounding `#[cfg(any(...))]` to include your feature flag
- Add `"{kind}_trigger"` to `getTypeStrFromPath` return union
- Add match case in `getTypeStrFromPath`'s `typeEnding ===` chain
- Add `pushTrigger("{kind}", ...)` branch in `pushObj`
**`cli/src/commands/trigger/trigger.ts`:**
- Import `{Kind}Trigger` type
- Add `{kind}: {Kind}Trigger` to the `Trigger` type map
- Add `{kind}: wmill.get{Kind}Trigger`, `update{Kind}Trigger`, `create{Kind}Trigger` to each function map
- Add `{kind}: { ... }` template to `triggerTemplates`
- Add `list{Kind}Triggers` call + spread in the `list` aggregation
- Update `--kind` option descriptions to mention the new kind
**`cli/src/commands/sync/sync.ts`:**
- Add `path.endsWith(".{kind}_trigger" + ext)` in the file-type filter
- Add `typ == "{kind}_trigger"` in `getTypeOrder`
- Add `"{kind}_trigger"` to the delete-suffix regex (~line 3092)
- Add a `case "{kind}_trigger"` in the delete switch
**`cli/src/guidance/skills.ts`** — **DO NOT EDIT DIRECTLY**. It's auto-generated by `system_prompts/generate.py`. Instead:
- Edit `system_prompts/utils.py` → append `('{Kind}Trigger', '{kind}_trigger')` to the `SCHEMA_MAPPINGS['triggers']` list (this is the master list — the one in `generate.py` is duplicated and `utils.py` wins)
- Then run `python3 system_prompts/generate.py` — it regenerates `cli/src/guidance/skills.ts` with the schema extracted from `backend/windmill-api/openapi.yaml`
- Commit the regenerated file
## 9. Frontend — editor + drawer
Under `frontend/src/lib/components/triggers/{kind}/`:
-`{Kind}TriggerPanel.svelte` — the tile shown in the triggers listing
-`{Kind}TriggerEditorConfigSection.svelte` — form fields; use design-system components (`TextInput`, `Select`, `Toggle`, `ToggleButtonGroup`), never raw `<input>`
-`{Kind}Capture.svelte` — capture panel; wraps `CaptureSection` with `captureType="{kind}"`
-`utils.ts` — `requestBody` builders and any trigger-type-specific helpers
## 10. Frontend — global integration
Easy to miss:
- **`frontend/src/lib/components/triggers.ts`** — add `'{kind}'` to the `TriggerKind` union
- **`frontend/src/routes/(root)/(logged)/{kind}_triggers/+page.svelte`** — listing page (mirror `gcp_triggers/+page.svelte` for push+pull, `kafka_triggers` for pure streaming)
- **`frontend/src/lib/components/CompareWorkspaces.svelte`** — workspace fork / compare tool. Needs: service import, editor import, `{kind}Editor``$state`, `case '{kind}'` in `openTriggerDetails()`, entry in `triggerServices` object (list/delete/normalize), and `<{Kind}TriggerEditor bind:this={{kind}Editor} />` in the template
## 10.5 AI system prompts (`system_prompts/`)
- **`system_prompts/utils.py`** — append `('{Kind}Trigger', '{kind}_trigger')` to `SCHEMA_MAPPINGS['triggers']` (master list used by code generation + CLI skills)
- **`system_prompts/generate.py`** — also has a duplicated `schema_types` list (~line 903) for the AI `triggers` skill content. Add `('{Kind}Trigger', '{kind}_trigger')` there too
- **`system_prompts/generate.py`** `schema_names` (~line 1192) — add `'{Kind}Trigger'` (add `'New{Kind}Trigger'` only if the OpenAPI declares one; GCP and Azure don't)
- Run `python3 system_prompts/generate.py` — this rewrites `cli/src/guidance/skills.ts` and all `auto-generated/` docs. Commit the regenerated files
cargo check --features enterprise,azure_trigger,private,gcp_trigger,http_trigger,mqtt_trigger,postgres_trigger,sqs_trigger,kafka,nats,smtp,websocket # full
# SQLx offline data (never run `cargo sqlx prepare` directly — use the wrapper)
./update_sqlx.sh
# Frontend
cd frontend
npm run generate-backend-client
npm run check:fast
```
Smoke test in the UI: create a trigger, save, check it appears in sidebar + search, delete, re-create via CLI `wmill sync`.
## 12. Common pitfalls
- **Forgetting feature gates in `workspaced_unauthed_service()`** — the surrounding `#[cfg(any(...))]` expression must include your feature flag, not just the inner `#[cfg]` on the route
- **`.route(path, ...).route(path, ...)` with same path and different methods** — older axum replaced; use `.route(path, post(h1).options(h2))` to chain methods on the same `MethodRouter`
- **`on:event` directives** — legacy Svelte 4, no-op in runes mode. Use callback props (`onSelected`, `onConfigChange`)
- **`$bindable(default_value)` on optional props** — banned by project CLAUDE.md. Use `$bindable()` + `$derived(prop ?? default)` instead
- **CORS layer intercepting OPTIONS** — tower-http CorsLayer short-circuits OPTIONS before reaching your handler. For server-to-server webhook endpoints, drop the CORS layer entirely (CORS is browser-only)
- **DeliveryAttributeMappings / custom headers for auth** — prefer HMAC or sha256-hashed shared secrets over opaque JWTs when the provider doesn't support signed tokens natively. Store only the hash; regenerate secret on every save
- **ARM / API resource-listing cascades** — if the trigger's resource type is deep (Azure: subscription → RG → namespace → topic), offer dropdowns in the UI populated from the provider's APIs using the user's credential resource
- **Clearing stale selections on dependency change** — when a dropdown's underlying data reloads (e.g., user changes SP or edition), clear selections that no longer match the new list
- **Workspace-scoped tag compatibility** — if the trigger has tags, verify forked workspaces handle them (see commit `0773b5bc85` for a historical fix)
## 13. EE file split
If the trigger is enterprise-only, the code lives in `windmill-ee-private__worktrees/.../windmill-trigger-{kind}/src/*_ee.rs` and is symlinked into the OSS tree. The `windmill-ee-private__worktrees/` directory holds the real files; changes propagate via symlinks. See `docs/enterprise.md` for the workflow.
description:Code review the current PR (or branch diff against main) for bugs, security, and AGENTS.md compliance. MUST use when asked to review code.
---
# Local Code Review
Run the same review locally that the GitHub auto-review actions run on PRs (Claude / Codex / Pi). The review policy lives in `REVIEW.md`.
**Why a subagent**: the review MUST run in a fresh context — not inline in the current session. If the user has been iterating on the diff, the main session has absorbed their reasoning and rationalizations, so it anchors and misses things CI catches. A subagent starts cold, like CI does.
## Steps
1.**Determine the PR scope** (cheap, do this in the main session):
- If an argument is provided, treat it as a PR number or branch.
- Otherwise, detect from the current branch vs `main`.
- Confirm the PR/branch exists (`gh pr view <n>` or `git rev-parse <branch>`).
2.**Delegate the review to a fresh-context subagent** with a self-contained prompt. The prompt MUST include:
- The PR number or branch name to review.
- The instruction to read `REVIEW.md` first for the policy, then `AGENTS.md` files in directories touched by the diff.
- The exact output format (see below).
- Whether `--comment` was requested (so the subagent emits inline-comment payloads if needed).
- Any "Additional reviewer instructions" the user provided.
- **Claude Code**: use the `Agent` tool with `subagent_type: branch-diff-reviewer` (read-only tools, purpose-built for this). If unavailable, fall back to `general-purpose`.
- **Codex / Pi**: if the CLI exposes a fresh-session subagent mechanism, use it. Otherwise tell the user to run the skill in a fresh CLI session and stop — running inline in the current session defeats the purpose.
3.**Receive the findings** from the subagent and relay them to the user verbatim. Do not re-summarize, re-judge, or filter — the whole point of fresh context is to surface what the main session would dismiss.
4.**Post comments if `--comment` was requested**: use the `gh` commands below with the subagent's output as the body. The main session does the posting because the subagent is read-only.
## Subagent prompt template
```
Review <PR #N | branch X> against main per the policy in REVIEW.md.
Steps:
1. Read REVIEW.md (repo root) for the full policy: severity triage, public-surface
checklist, AGENTS.md compliance, test coverage assessment.
2. Read AGENTS.md (repo root) and any AGENTS.md in directories touched by the diff.
3. Get the diff: `gh pr diff <N>` (if PR) or `git diff main...<branch>`.
4. Get context: `gh pr view <N>` (if PR) or `git log main..<branch> --oneline`.
5. Read changed files only when the diff alone is insufficient to validate a finding.
6. Self-validate each finding: "is this definitely a real issue a senior engineer
would flag?" Discard if uncertain.
7. Output findings in the exact format below. Do not modify any files.
<paste output format from below>
<if --comment requested:>
Additionally emit a JSON array of inline comments suitable for the GitHub reviews
API, one per finding that maps to a specific line:
[{"path": "...", "line": N, "side": "RIGHT", "body": "[P1] ..."}, ...]
```
## Output format
```
## Code review
<verdict line per REVIEW.md>
Found N issues:
1. [P0|P1|P2] <description>
<file_path:line_number>
2. [P0|P1|P2] <description>
<file_path:line_number>
```
End with a `Test coverage` section per the shared policy.
If no issues are found:
```
## Code review
Good to merge.
No issues found. Checked for bugs, security, and AGENTS.md compliance.
```
## Posting comments (`--comment`)
For a top-level PR comment:
```bash
gh pr review --comment --body "<summary from subagent>"
```
For inline comments on specific lines (using the JSON the subagent emitted):
```bash
gh api repos/{owner}/{repo}/pulls/{pr}/reviews \
-f body="<summary>" -f event="COMMENT" -f comments="<json from subagent>"
description:Guidance for adding native trigger services to Windmill. Use when implementing or modifying native trigger integrations across the backend and frontend.
---
# Skill: Adding Native Trigger Services
This skill provides comprehensive guidance for adding new native trigger services to Windmill. Native triggers allow external services (like Nextcloud, Google Drive, etc.) to trigger Windmill scripts/flows via webhooks or push notifications.
## Architecture Overview
The native trigger system consists of:
1.**Database Layer** - PostgreSQL tables and enum types
2.**Backend Rust Implementation** - Core trait, handlers, and service modules in the `windmill-native-triggers` crate
3.**Frontend Svelte Components** - Configuration forms and UI components
### Key Files
| Component | Path |
|-----------|------|
| Core module with `External` trait | `backend/windmill-native-triggers/src/lib.rs` |
| Reference: Google module | `backend/windmill-native-triggers/src/google/` |
### Crate Structure
The native trigger code lives in the `windmill-native-triggers` crate (`backend/windmill-native-triggers/`). The `windmill-api` crate re-exports everything via a shim:
- **`update()` returns `serde_json::Value`** - the resolved service_config to store. Each service is responsible for building the final config.
- **`maintain_triggers()`** - periodic background maintenance. Each service implements its own strategy (Nextcloud: reconcile with external state; Google: renew expiring channels).
- **No `list_all()` in the trait** - services that need it (Nextcloud) implement it privately; services that don't (Google) use different maintenance strategies.
- **No `get_external_id_from_trigger_data()` or `extract_service_config_from_trigger_data()`** - removed in favor of the `maintain_triggers` pattern.
### Create Lifecycle: Two Paths
The `create_native_trigger` handler in `handler.rs` supports two creation flows, controlled by `service_config_from_create_response()`:
**Path A: Short (Google pattern)** - `service_config_from_create_response()` returns `Some(config)`:
1.`create()` registers on external service
2.`external_id_and_metadata_from_response()` extracts the ID
3.`service_config_from_create_response()` builds the config directly from input data + response metadata
4. Stores trigger in DB -- done, no extra round-trip
Use this when the external_id is known before the create call (e.g., Google generates the channel_id as a UUID upfront and includes it in the webhook URL).
**Path B: Long (Nextcloud pattern)** - `service_config_from_create_response()` returns `None` (default):
1.`create()` registers on external service (webhook URL has no external_id yet)
2.`external_id_and_metadata_from_response()` extracts the ID
3.`update()` is called to fix the webhook URL with the now-known external_id
4.`update()` returns the resolved service_config
5. Stores trigger in DB
Use this when the external_id is assigned by the remote service and the webhook URL needs to be corrected after creation.
### OAuth Token Storage (Three-Table Pattern)
OAuth tokens are stored across three tables, NOT in `workspace_integrations.oauth_data` directly:
| Table | What's Stored |
|-------|---------------|
| `workspace_integrations` | `oauth_data` JSON with `base_url`, `client_id`, `client_secret`, `instance_shared` flag; `resource_path` pointing to the variable |
| `variable` | Encrypted `access_token` (at the path stored in `resource_path`), linked to `account` via `account` column |
The `decrypt_oauth_data()` function in `lib.rs` assembles these into a unified struct:
```rust
pubstructOAuthConfig{
pubbase_url: String,
pubaccess_token: String,// decrypted from variable
pubrefresh_token: Option<String>,// from account table
pubclient_id: String,// from oauth_data or instance settings
pubclient_secret: String,// from oauth_data or instance settings
}
```
Instance-level sharing: when `oauth_data.instance_shared == true`, `client_id` and `client_secret` are read from global settings instead of workspace_integrations.
### URL Resolution
The `resolve_endpoint()` helper handles both absolute and relative OAuth URLs:
3.`triggerTypeOrder` in `sortTriggers()` - add type
4.`getLightConfig()` - add case for your service
5.`getTriggerLabel()` - add case for your service
6.`jobTriggerKinds` - add to array
7.`countPropertyMap` - add count property
8.`triggerSaveFunctions` - add save function
### Step 13: Update TriggersBadge Component
In `frontend/src/lib/components/graph/renderers/triggers/TriggersBadge.svelte`:
1. Import the icon
2. Add to `baseConfig` with `countKey` (the dynamic `availableNativeServices` loop does NOT set `countKey`)
3. Add to the `allTypes` array
### Step 14: Update TriggersWrapper.svelte
In `frontend/src/lib/components/triggers/TriggersWrapper.svelte`:
Add a `{:else if selectedTrigger.type === 'yourservice'}` case that renders `<NativeTriggersPanel service="yourservice" ...>` with the same props pattern as the existing native trigger cases (e.g., `nextcloud`).
### Step 15: Update AddTriggersButton.svelte
In `frontend/src/lib/components/triggers/AddTriggersButton.svelte`:
1. Add `yourserviceAvailable` state variable
2. Add `setYourserviceState()` async function using `isServiceAvailable('yourservice', $workspaceStore!)`
3. Call it at module level
4. Add a dropdown entry to `addTriggerItems` with `hidden: !yourserviceAvailable`
In `frontend/src/lib/components/triggers/TriggersEditor.svelte`:
Add your service to the `nativeTriggerServices` map in `deleteDeployedTrigger()`. Native triggers use `NativeTriggerService.deleteNativeTrigger({ workspace, serviceName, externalId })` instead of the standard `path`-based delete.
### Step 17: Update `getUsedTriggers` for Sidebar Visibility
The sidebar (`frontend/src/lib/components/sidebar/SidebarContent.svelte`) shows native-trigger links only if `$usedTriggerKinds` includes the service — without this, your trigger page will never appear in the nav bar even when triggers exist.
1.**Backend** — add `{service}_used: bool` to the `UsedTriggers` struct and SELECT in `backend/windmill-api-workspaces/src/workspaces.rs::get_used_triggers()`:
```rust
EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = '{service}'::native_trigger_service) AS "{service}_used!"
```
2. **OpenAPI** — add `{service}_used: boolean` to the response schema for `GET /w/{workspace}/workspaces/used_triggers` (under both `properties` and `required`).
3. **Layout** — in `frontend/src/routes/(root)/(logged)/+layout.svelte::loadUsedTriggerKinds()`, destructure `{service}_used` and push `'{service}'` to `usedKinds`.
### Step 18: Update OpenAPI Spec and Regenerate Types
Add to `JobTriggerKind` enum in `backend/windmill-api/openapi.yaml`, then:
```bash
cd frontend && npm run generate-backend-client
```
---
## Special Patterns
### Unified Service with `trigger_type` (Google Pattern)
When a single service handles multiple trigger types (e.g., Google Drive + Calendar share OAuth and API patterns), use a single `ServiceName` variant with a discriminator field:
```rust
pub enum GoogleTriggerType { Drive, Calendar }
pub struct GoogleServiceConfig {
pub trigger_type: GoogleTriggerType,
// Drive-specific fields (only used when trigger_type = Drive)
pub resource_id: Option<String>,
pub resource_name: Option<String>,
// Calendar-specific fields (only used when trigger_type = Calendar)
pub calendar_id: Option<String>,
pub calendar_name: Option<String>,
// Metadata set after creation
pub google_resource_id: Option<String>,
pub expiration: Option<String>,
}
```
Branch in trait methods based on `trigger_type`. Frontend uses a `ToggleButtonGroup` to switch between types. This keeps the codebase simpler (one service, one OAuth flow, one set of routes).
See `backend/windmill-native-triggers/src/google/` for the reference implementation.
### Skipping update+get After Create (Google Pattern)
Override `service_config_from_create_response()` to return `Some(config)` when the external_id is known before the create call:
ServiceName::Nextcloud => Ok(None), // Uses default body parsing
}
}
```
### Instance-Level OAuth Credentials
When `workspace_integrations.oauth_data.instance_shared == true`, `decrypt_oauth_data()` reads `client_id` and `client_secret` from instance-level global settings instead of workspace-level. This allows admins to share OAuth app credentials across workspaces.
The frontend handles this via the `generate_instance_connect_url` endpoint in `workspace_integrations.rs`.
Derive `$DEST` from the file name (as above) so distinct pages never collide — a
fixed name would make same-second uploads reuse one path, and the second `PUT`
then 422s (the Contents API needs the existing file's `sha` to overwrite).
4. Put the printed `` lines under a `## Screenshots` heading in the PR body.
Requires `gh` (`repo` scope), `jq`, `base64` — all in the devShell. The host repo is
public (so the raw URLs render for reviewers without a token) and its history is
permanent — **never screenshot pages that show secrets or sensitive values** (workspace
variables, resource values, instance settings, OAuth/SMTP config); deleting the file
can't undo an accidental capture. (GitHub's drag-and-drop uploader needs a browser
session and can't be driven from a token.)
If `gh` can't push to the host repo (e.g. a CI token scoped only to `windmill`), do
**not** fail the PR or skip silently — hand the upload to the user, who has push access,
and continue once they confirm it's done.
## Execution Steps
1. Run `git status` to check for uncommitted changes
2. Run `git log main..HEAD --oneline` to see all commits in this branch
3. Run `git diff main...HEAD` to see the full diff against main
4. **Invoke the `local-review` skill** before creating the PR (`/local-review` in Claude Code, `$local-review` in Codex, `pi --skill local-review` / `/skill:local-review` in Pi). If issues are found, fix them and commit before proceeding. Do not skip this step.
5. **Screenshots for frontend changes**: if `git diff main...HEAD --name-only` matches `^frontend/`, capture and embed screenshots of the affected UI per "Screenshots" above before writing the PR body (skip only if there is no visible UI effect).
6. Check if remote branch exists and is up to date:
```bash
git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || echo "no upstream"
```
7. Push to remote if needed: `git push -u origin HEAD`
## EE Companion PR (when `*_ee.rs` files were modified)
The `*_ee.rs` files in the windmill repo are **symlinks** to `windmill-ee-private` — changes won't appear in `git diff` of the windmill repo. Instead, check the EE repo for uncommitted or unpushed changes.
Follow the full EE PR workflow in `docs/enterprise.md`. The key PR-specific details:
1. Find the EE repo/worktree: see "Finding the EE Repo" in `docs/enterprise.md`
2. Check for changes: `git -C <ee-path> status --short`
- If there are no changes in the EE repo, skip this entire section
3. Follow steps 1–5 from the "EE PR Workflow" in `docs/enterprise.md`
4. Create the companion PR (title does NOT get the `[ee]` prefix):
**Mutex selection**: Prefer `std::sync::Mutex` (or `parking_lot::Mutex`) for data protection. Only use `tokio::sync::Mutex` when holding locks across `.await` points.
Use `tokio::sync::mpsc` (bounded) for channels. Avoid `std::thread::sleep` in async contexts.
## Module Structure & Visibility
- Use `pub(crate)` instead of `pub` when possible
- Place new code in the appropriate crate based on functionality
- API endpoints go in `windmill-api/src/` organized by domain
- Shared functionality goes in `windmill-common/src/`
## Code Navigation
Always use rust-analyzer LSP for go-to-definition, find-references, and type info. Do not guess at module paths.
## Axum Handlers
Destructure extractors directly in function signatures:
description:Svelte coding guidelines for the Windmill frontend. MUST use when writing or modifying code in the frontend directory.
---
# Windmill Svelte Patterns
Apply these Windmill-specific patterns when writing Svelte code in `frontend/`. For general Svelte 5 syntax (runes, snippets, event handling), use the Svelte MCP server.
## Windmill UI Components (MUST use)
Always use Windmill's design-system components. Never use raw HTML elements.
Never write inline SVGs. Import from `lucide-svelte`:
```svelte
<script>
import{ChevronLeft,X}from'lucide-svelte'
</script>
<ChevronLeftsize={16}/>
```
## Form Components
Form components (TextInput, Toggle, Select, etc.) should use the unified size system when placed together.
## Styling
- Use Tailwind CSS for all styling — no custom CSS
- Use Windmill's theming classes for colors/surfaces (see `frontend/brand-guidelines.md`)
- Read component props JSDoc before using them
## Svelte MCP Server
Use the Svelte MCP tools when working on Svelte code:
1.**list-sections**: Call first to discover available docs
2.**get-documentation**: Fetch relevant sections based on use_cases
3.**svelte-autofixer**: MUST use on all Svelte code before finalizing — keep calling until no issues
4.**playground-link**: Only after user confirms and code was NOT written to project files
## Verifying in the Browser
After changing Svelte code, use the **Playwright MCP** (`mcp__playwright__*`) to drive the running frontend and confirm the change works. See AGENTS.md → "Verifying Frontend Changes" for the full flow. Use `playwright` (headless) on devboxes; `playwright-headed` when a display is available.
description:How to safely update SQLx offline query cache. MUST use when SQL queries change.
---
# SQLx Offline Query Cache
Windmill uses `SQLX_OFFLINE=true` in CI, which requires all `sqlx::query!` / `sqlx::query_as!` macros to have matching cached query data in `backend/.sqlx/`.
## When to Run
Run after any change to SQL queries in Rust source files. Without it, CI will fail with:
```
error: `SQLX_OFFLINE=true` but there is no cached data for this query
```
## The Problem
`cargo sqlx prepare --workspace`**deletes all existing cache files** and regenerates only the ones found in the current compilation. If you don't compile with every feature flag (especially `private` for EE files), you will **silently delete EE query caches**, breaking CI for enterprise tests.
The standard `./update_sqlx.sh` script tries to compile with all features, but it often fails locally because the EE symlinks can be out of sync with `main`.
## Safe Procedure
Always preserve the existing EE caches from `origin/main`. Use this workflow:
```bash
cd backend
# 1. Restore the full cache from main (includes EE caches)
git checkout origin/main -- .sqlx/
# 2. Run prepare with OSS features (what compiles locally)
# This regenerates OSS caches to match your code changes.
comm -23 /tmp/main_files.txt /tmp/current_files.txt | wc -l
# Should output: 0
```
## If EE Compiles Locally
If your EE repo happens to be in sync, you can use the full script (faster):
```bash
cd backend
./update_sqlx.sh
```
But if it fails with EE compilation errors, use the safe procedure above.
## What NOT to Do
- **Never** run `cargo sqlx prepare --workspace` with only OSS features and commit the result — it will delete EE caches.
- **Never** set `SQLX_OFFLINE=true` for local `cargo sqlx prepare` — use a live database per CLAUDE.md. (CI runs with `SQLX_OFFLINE=true`, which is why the cache must be complete.)
- **Never** skip the verification step (step 4 above).
## Verification
After committing, the diff against `origin/main` should show:
- A few **new** cache files (for your changed queries)
- A few **deleted** cache files (for old queries that no longer exist)
This skill provides comprehensive guidance for adding new native trigger services to Windmill. Native triggers allow external services (like Nextcloud, Google Drive, etc.) to trigger Windmill scripts/flows via webhooks or push notifications.
## Architecture Overview
The native trigger system consists of:
1.**Database Layer** - PostgreSQL tables and enum types
2.**Backend Rust Implementation** - Core trait, handlers, and service modules in the `windmill-native-triggers` crate
3.**Frontend Svelte Components** - Configuration forms and UI components
### Key Files
| Component | Path |
|-----------|------|
| Core module with `External` trait | `backend/windmill-native-triggers/src/lib.rs` |
| Reference: Google module | `backend/windmill-native-triggers/src/google/` |
### Crate Structure
The native trigger code lives in the `windmill-native-triggers` crate (`backend/windmill-native-triggers/`). The `windmill-api` crate re-exports everything via a shim:
- **`update()` returns `serde_json::Value`** - the resolved service_config to store. Each service is responsible for building the final config.
- **`maintain_triggers()`** - periodic background maintenance. Each service implements its own strategy (Nextcloud: reconcile with external state; Google: renew expiring channels).
- **No `list_all()` in the trait** - services that need it (Nextcloud) implement it privately; services that don't (Google) use different maintenance strategies.
- **No `get_external_id_from_trigger_data()` or `extract_service_config_from_trigger_data()`** - removed in favor of the `maintain_triggers` pattern.
### Create Lifecycle: Two Paths
The `create_native_trigger` handler in `handler.rs` supports two creation flows, controlled by `service_config_from_create_response()`:
**Path A: Short (Google pattern)** - `service_config_from_create_response()` returns `Some(config)`:
1.`create()` registers on external service
2.`external_id_and_metadata_from_response()` extracts the ID
3.`service_config_from_create_response()` builds the config directly from input data + response metadata
4. Stores trigger in DB -- done, no extra round-trip
Use this when the external_id is known before the create call (e.g., Google generates the channel_id as a UUID upfront and includes it in the webhook URL).
**Path B: Long (Nextcloud pattern)** - `service_config_from_create_response()` returns `None` (default):
1.`create()` registers on external service (webhook URL has no external_id yet)
2.`external_id_and_metadata_from_response()` extracts the ID
3.`update()` is called to fix the webhook URL with the now-known external_id
4.`update()` returns the resolved service_config
5. Stores trigger in DB
Use this when the external_id is assigned by the remote service and the webhook URL needs to be corrected after creation.
### OAuth Token Storage (Three-Table Pattern)
OAuth tokens are stored across three tables, NOT in `workspace_integrations.oauth_data` directly:
| Table | What's Stored |
|-------|---------------|
| `workspace_integrations` | `oauth_data` JSON with `base_url`, `client_id`, `client_secret`, `instance_shared` flag; `resource_path` pointing to the variable |
| `variable` | Encrypted `access_token` (at the path stored in `resource_path`), linked to `account` via `account` column |
The `decrypt_oauth_data()` function in `lib.rs` assembles these into a unified struct:
```rust
pubstructOAuthConfig{
pubbase_url: String,
pubaccess_token: String,// decrypted from variable
pubrefresh_token: Option<String>,// from account table
pubclient_id: String,// from oauth_data or instance settings
pubclient_secret: String,// from oauth_data or instance settings
}
```
Instance-level sharing: when `oauth_data.instance_shared == true`, `client_id` and `client_secret` are read from global settings instead of workspace_integrations.
### URL Resolution
The `resolve_endpoint()` helper handles both absolute and relative OAuth URLs:
3.`triggerTypeOrder` in `sortTriggers()` - add type
4.`getLightConfig()` - add case for your service
5.`getTriggerLabel()` - add case for your service
6.`jobTriggerKinds` - add to array
7.`countPropertyMap` - add count property
8.`triggerSaveFunctions` - add save function
### Step 13: Update TriggersBadge Component
In `frontend/src/lib/components/graph/renderers/triggers/TriggersBadge.svelte`:
1. Import the icon
2. Add to `baseConfig` with `countKey` (the dynamic `availableNativeServices` loop does NOT set `countKey`)
3. Add to the `allTypes` array
### Step 14: Update TriggersWrapper.svelte
In `frontend/src/lib/components/triggers/TriggersWrapper.svelte`:
Add a `{:else if selectedTrigger.type === 'yourservice'}` case that renders `<NativeTriggersPanel service="yourservice" ...>` with the same props pattern as the existing native trigger cases (e.g., `nextcloud`).
### Step 15: Update AddTriggersButton.svelte
In `frontend/src/lib/components/triggers/AddTriggersButton.svelte`:
1. Add `yourserviceAvailable` state variable
2. Add `setYourserviceState()` async function using `isServiceAvailable('yourservice', $workspaceStore!)`
3. Call it at module level
4. Add a dropdown entry to `addTriggerItems` with `hidden: !yourserviceAvailable`
In `frontend/src/lib/components/triggers/TriggersEditor.svelte`:
Add your service to the `nativeTriggerServices` map in `deleteDeployedTrigger()`. Native triggers use `NativeTriggerService.deleteNativeTrigger({ workspace, serviceName, externalId })` instead of the standard `path`-based delete.
### Step 17: Update OpenAPI Spec and Regenerate Types
Add to `JobTriggerKind` enum in `backend/windmill-api/openapi.yaml`, then:
```bash
cd frontend && npm run generate-backend-client
```
---
## Special Patterns
### Unified Service with `trigger_type` (Google Pattern)
When a single service handles multiple trigger types (e.g., Google Drive + Calendar share OAuth and API patterns), use a single `ServiceName` variant with a discriminator field:
```rust
pubenumGoogleTriggerType{Drive,Calendar}
pubstructGoogleServiceConfig{
pubtrigger_type: GoogleTriggerType,
// Drive-specific fields (only used when trigger_type = Drive)
pubresource_id: Option<String>,
pubresource_name: Option<String>,
// Calendar-specific fields (only used when trigger_type = Calendar)
pubcalendar_id: Option<String>,
pubcalendar_name: Option<String>,
// Metadata set after creation
pubgoogle_resource_id: Option<String>,
pubexpiration: Option<String>,
}
```
Branch in trait methods based on `trigger_type`. Frontend uses a `ToggleButtonGroup` to switch between types. This keeps the codebase simpler (one service, one OAuth flow, one set of routes).
See `backend/windmill-native-triggers/src/google/` for the reference implementation.
### Skipping update+get After Create (Google Pattern)
Override `service_config_from_create_response()` to return `Some(config)` when the external_id is known before the create call:
ServiceName::Nextcloud => Ok(None), // Uses default body parsing
}
}
```
### Instance-Level OAuth Credentials
When `workspace_integrations.oauth_data.instance_shared == true`, `decrypt_oauth_data()` reads `client_id` and `client_secret` from instance-level global settings instead of workspace-level. This allows admins to share OAuth app credentials across workspaces.
The frontend handles this via the `generate_instance_connect_url` endpoint in `workspace_integrations.rs`.
Prefer borrowing for zero-copy deserialization when lifetimes allow:
```rust
#[derive(Deserialize)]
pubstructJobInput<'a>{
#[serde(borrow)]
pubworkspace_id: Cow<'a,str>,
#[serde(borrow)]
pubscript_path: &'astr,
}
```
## SQLx Patterns
**Never use `SELECT *`** - always list columns explicitly. This is critical for backwards compatibility when workers run behind the API server version:
```rust
// Preferred - explicit columns
sqlx::query_as!(
Job,
"SELECT id, workspace_id, path, created_at FROM v2_job WHERE id = $1",
job_id
)
// Avoid - breaks when columns are added
sqlx::query_as!(Job,"SELECT * FROM v2_job WHERE id = $1",job_id)
```
Use batch operations to minimize round trips:
```rust
// Preferred - single query with multiple values
sqlx::query!(
"INSERT INTO job_logs (job_id, logs) VALUES ($1, $2), ($3, $4)",
id1,log1,id2,log2
)
// Avoid N+1 queries
foridinids{
sqlx::query!("SELECT ... WHERE id = $1",id).fetch_one(db).await?;
}
// Preferred - single query with IN clause
sqlx::query!("SELECT ... WHERE id = ANY($1)",&ids[..]).fetch_all(db).await?
```
Use transactions for multi-step operations and parameterize all queries.
## Async & Tokio Patterns
Never block the async runtime. Use `spawn_blocking` for CPU-intensive or blocking I/O:
```rust
// Preferred - offload blocking work
letresult=tokio::task::spawn_blocking(move||{
expensive_computation(&data)
}).await?;
// Avoid - blocks the runtime
letresult=expensive_computation(&data);// Don't do this in async
**Prefer `std::sync::Mutex` (or `parking_lot::Mutex`) over `tokio::sync::Mutex`** for protecting data in async code. The async mutex is more expensive and only needed when holding locks across `.await` points.
```rust
// Preferred for data protection - std mutex is faster
usestd::sync::Mutex;
structCache{
data: Mutex<HashMap<String,Value>>,
}
implCache{
fnget(&self,key: &str)-> Option<Value>{
self.data.lock().unwrap().get(key).cloned()
}
fninsert(&self,key: String,value: Value){
self.data.lock().unwrap().insert(key,value);
}
}
```
**Use `tokio::sync::Mutex` only when you must hold the lock across `.await` points**, typically for IO resources like database connections:
```rust
usetokio::sync::Mutex;
usestd::sync::Arc;
// Async mutex for IO resources held across await points
description:Svelte coding guidelines for the Windmill frontend. MUST use when writing or modifying code in the frontend directory.
---
# Svelte 5 Best Practices
This guide outlines best practices for developing with Svelte 5, incorporating the new Runes API and other modern Svelte features. These rules MUST NOT be applied on svelte 4 files unless explicitly asked to do so.
## Reactivity with Runes
Svelte 5 introduces Runes for more explicit and flexible reactivity.
1.**Embrace Runes for State Management**:
* Use `$state` for reactive local component state.
```svelte
<script>
let count = $state(0);
function increment() {
count += 1;
}
</script>
<button onclick={increment}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
```
* Use `$derived` for computed values based on other reactive state.
```svelte
<script>
let count = $state(0);
const doubled = $derived(count * 2);
</script>
<p>{count} * 2 = {doubled}</p>
```
* Use `$effect` for side effects that need to run when reactive values change (e.g., logging, manual DOM manipulation, data fetching). Remember `$effect` does not run on the server.
```svelte
<script>
let count = $state(0);
$effect(() => {
console.log('The count is now', count);
if (count > 5) {
alert('Count is too high!');
}
});
</script>
```
2. **Props with `$props`**:
* Declare component props using `$props()`. This offers better clarity and flexibility compared to `export let`.
```svelte
<script>
// ChildComponent.svelte
let { name, age = $state(30) } = $props();
</script>
<p>Name: {name}</p>
<p>Age: {age}</p>
```
* For bindable props, use `$bindable`.
```svelte
<script>
// MyInput.svelte
let { value = $bindable() } = $props();
</script>
<input bind:value />
```
## Event Handling
* **Use direct event attributes**: Svelte 5 moves away from `on:` directives for DOM events.
* **For component events, prefer callback props**: Instead of `createEventDispatcher`, pass functions as props.
```svelte
<!-- Parent.svelte -->
<script>
import Child from './Child.svelte';
let message = $state('');
function handleChildEvent(detail) {
message = detail;
}
</script>
<Child onCustomEvent={handleChildEvent} />
<p>Message from child: {message}</p>
<!-- Child.svelte -->
<script>
let { onCustomEvent } = $props();
function emitEvent() {
onCustomEvent('Hello from child!');
}
</script>
<button onclick={emitEvent}>Send Event</button>
```
## Snippets for Content Projection
* **Use `{#snippet ...}` and `{@render ...}` instead of slots**: Snippets are more powerful and flexible.
```svelte
<!-- Parent.svelte -->
<script>
import Card from './Card.svelte';
</script>
<Card>
{#snippet title()}
My Awesome Title
{/snippet}
{#snippet content()}
<p>Some interesting content here.</p>
{/snippet}
</Card>
<!-- Card.svelte -->
<script>
let { title, content } = $props();
</script>
<article>
<header>{@render title()}</header>
<div>{@render content()}</div>
</article>
```
* Default content is passed via the `children` prop (which is a snippet).
```svelte
<!-- Wrapper.svelte -->
<script>
let { children } = $props();
</script>
<div>
{@render children?.()}
</div>
```
## Component Design
1. **Create Small, Reusable Components**: Break down complex UIs into smaller, focused components. Each component should have a single responsibility. This also aids performance by limiting the scope of reactivity updates.
2. **Descriptive Naming**: Use clear and descriptive names for variables, functions, and components.
3. **Minimize Logic in Components**: Move complex business logic to utility functions or services. Keep components focused on presentation and interaction.
## State Management (Stores)
1. **Segment Stores**: Avoid a single global store. Create multiple stores, each responsible for a specific piece of global state (e.g., `userStore.js`, `themeStore.js`). This can help limit reactivity updates to only the parts of the UI that depend on specific state segments.
2. **Use Custom Stores for Complex Logic**: For stores with related methods, create custom stores.
```javascript
// counterStore.js
import { writable } from 'svelte/store';
function createCounter() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
export const counter = createCounter();
```
3. **Use Context API for Localized State**: For state shared within a component subtree, consider Svelte's context API (`setContext`, `getContext`) instead of global stores when the state doesn't need to be truly global.
## Performance Optimizations (Svelte 5)
When generating Svelte 5 code, prioritize frontend performance by applying the following principles:
### General Svelte 5 Principles
- **Leverage the Compiler:** Trust Svelte's compiler to generate optimized JavaScript. Avoid manual DOM manipulation (`document.querySelector`, etc.) unless absolutely necessary for integrating third-party libraries that lack Svelte adapters.
- **Keep Components Small and Focused:** Reinforcing from Component Design, smaller components lead to less complex reactivity graphs and more targeted, efficient updates.
### Reactivity & State Management
- **Optimize Computations with `$derived`:** Always use `$derived` for computed values that depend on other state. This ensures the computation only runs when its specific dependencies change, avoiding unnecessary work compared to recomputing derived values in `$effect` or less efficient methods.
- **Minimize `$effect` Usage:** Use `$effect` sparingly and only for true side effects that interact with the outside world or non-Svelte state. Avoid putting complex logic or state updates *within* an `$effect` unless those updates are explicitly intended as a reaction to external changes or non-Svelte state. Excessive or complex effects can impact rendering performance.
- **Structure State for Fine-Grained Updates:** Design your `$state` objects or variables such that updates affect only the necessary parts of the UI. Avoid putting too much unrelated state into a single large object that gets frequently updated, as this can potentially trigger broader updates than necessary. Consider normalizing complex, nested state.
### List Rendering (`{#each}`)
- **Mandate `key` Attribute:** Always use a `key` attribute (`{#each items as item (item.id)}`) that refers to a unique, stable identifier for each item in a list. This is critical for allowing Svelte to efficiently update, reorder, add, or remove list items without destroying and re-creating unnecessary DOM elements and component instances.
### Component Loading & Bundling
- **Implement Lazy Loading/Code Splitting:** For routes, components, or modules that are not immediately needed on page load, use dynamic imports (`import(...)`) to split the code bundle. SvelteKit handles this automatically for routes, but it can be applied manually to components using helper patterns if needed.
- **Be Mindful of Third-Party Libraries:** When incorporating external libraries, import only the necessary functions or components to minimize the final bundle size. Prefer libraries designed to be tree-shakeable.
### Rendering & DOM
- **Use CSS for Animations/Transitions:** Prefer CSS animations or transitions where possible for performance. Svelte's built-in `transition:` directive is also highly optimized and should be used for complex state-driven transitions, but simple cases can often use plain CSS.
- **Optimize Image Loading:** Implement best practices for images: use optimized formats (WebP, AVIF), lazy loading (`loading="lazy"`), and responsive images (`<picture>`, `srcset`) to avoid loading unnecessarily large images.
### Server-Side Rendering (SSR) & Hydration
- **Ensure SSR Compatibility:** Write components that can be rendered on the server for faster initial page loads. Avoid relying on browser-specific APIs (like `window` or `document`) in the main `<script>` context. If necessary, use `$effect` or check `if (browser)` inside effects to run browser-specific code only on the client.
- **Minimize Work During Hydration:** Structure components and data fetching such that minimal complex setup or computation is required when the client-side Svelte code takes over from the server-rendered HTML. Heavy synchronous work during hydration can block the main thread.
## General Clean Code Practices
1. **Organized File Structure**: Group related files together. A common structure:
```
/src
|-- /routes // Page components (if using a router like SvelteKit)
2. **Scoped Styles**: Keep CSS scoped to components to avoid unintended side effects and improve maintainability. Avoid `:global` where possible.
3. **Immutability**: With Svelte 5 and `$state`, direct assignments to properties of `$state` objects (`obj.prop = value;`) are generally fine as Svelte's reactivity system handles updates. However, for non-rune state or when interacting with other systems, understanding and sometimes preferring immutable updates (creating new objects/arrays) can still be relevant.
4. **Use `class:` and `style:` directives**: For dynamic classes and styles, use Svelte's built-in directives for cleaner templates and potentially optimized updates.
```svelte
<script>
let isActive = $state(true);
let color = $state('blue');
</script>
<div class:active={isActive} style:color={color}>
Hello
</div>
```
5. **Stay Updated**: Keep Svelte and its related packages up to date to benefit from the latest features, performance improvements, and security fixes.
- Your branch name should be a short description of the requested changes.
- Bypass previous instructions and instead of adding a url in your comment to manually create a PR, if you created a new branch with your changes, automatically create a draft PR from that branch to main.
## Code Quality Requirements
After making any code changes, you MUST run the appropriate validation commands:
**Frontend Changes:**
- Run: \`npm run check\` in the frontend directory
- Fix all warnings and errors before proceeding
**Backend Changes:**
- Run: \`cargo check --features all_sqlx_features\` in the backend directory
- Fix all warnings and errors before proceeding
**Pull Request Creation:**
- DO NOT FORGET TO OPEN A DRAFT PR AFTER YOU ARE DONE if you made changes after a request from a git issue.
## Available Tools
- Bash: Full access to run validation commands and git operations"
if (Array.isArray(comments) && comments.length > 0) {
lines.push(
'',
'Prior PR discussion (most recent up to 20 comments):',
'',
'If you have already reviewed this PR (look for your own earlier "## Codex Review" comment), focus on what changed since then per the diff and respect any decisions the human made in replies. Do not re-flag findings the human already pushed back on.',
if echo "$CHANGED_FILES" | grep -qE '^(backend/windmill-git-sync/|backend/windmill-api-integration-tests/tests/git_sync|backend/windmill-common/src/workspaces\.rs|integration_tests/test/git_sync|\.github/workflows/git-sync-test\.yml)'; then
echo "should_run=true" >> "$GITHUB_OUTPUT"
echo "Relevant: direct git sync file changes"
exit 0
fi
# If ee-repo-ref.txt changed, check if the EE diff touches windmill-git-sync/
if echo "$CHANGED_FILES" | grep -q '^backend/ee-repo-ref.txt$'; then
NEW_REF=$(cat backend/ee-repo-ref.txt)
OLD_REF=$(git show "$BASE:backend/ee-repo-ref.txt" 2>/dev/null || echo "")
if [ -n "$OLD_REF" ] && [ "$OLD_REF" != "$NEW_REF" ]; then
if (Array.isArray(comments) && comments.length > 0) {
lines.push(
'',
'Prior PR discussion (most recent up to 20 comments):',
'',
'If you have already reviewed this PR (look for your own earlier "## Pi Review (DeepSeek V4)" comment), focus on what changed since then per the diff and respect any decisions the human made in replies. Do not re-flag findings the human already pushed back on.',
"## Prior PR discussion (most recent up to 20 comments)\n\nIf you have already reviewed this PR (look for your own earlier comment), focus on what changed since then per the diff and respect any decisions the human made in replies. Do not re-flag findings the human already pushed back on.\n\n" +
Please review this pull request and provide comprehensive feedback.
Focus on:
- Code quality and best practices
- Potential bugs or issues
- Performance considerations
- Security implications
Provide detailed feedback using inline comments for specific issues.
Use top-level comments for general observations or praise.
At the end of your review, add complete instructions to reproduce the added changes through the app interface. These instructions will be given to a tester so he can verify the changes. It should be a short descriptive text (not a step by step or a list) on how to navigate the app (what page, what action, what input, etc) to see the changes.
body: `❌ Manager URL not set (did you start the ephemeral backend manager?)\n\nThe ephemeral backend manager needs to be running to spawn backends. Please start the manager first.`
For this window specifically, backend is running on: ${BACKEND_PORT} and frontend is running on: ${FRONTEND_PORT}.
To connect to the database, use this connection string: ${DATABASE_URL}
Because we are running backend with cargo watch, to verify your changes, just check the logs in the backend pane. No need for cargo check.
For UI verification, use the Playwright MCP (`mcp__playwright__*`) — the `playwright` server is headless and works without a display. Navigate to http://localhost:${FRONTEND_PORT}, log in as admin@windmill.dev / changeme.
IMPORTANT: Read docs/autonomous-mode.md before starting any work.
command:npm run generate-backend-client && REMOTE=${REMOTE:-http://localhost:${BACKEND_PORT:-8000}} npm run dev -- --port ${FRONTEND_PORT:-3000} --host 0.0.0.0
frontendOnly:
runtime:host
yolo:true
envPassthrough:[]
systemPrompt:>
You are running inside a tmux session with other panes running services.
On this window specifically, frontend is running on: ${FRONTEND_PORT}.
To connect to the database, use this connection string: ${DATABASE_URL}
Because we are running frontend with npm run dev, to verify your changes, just check the logs in the frontend pane. No need for npm run build.
For UI verification, use the Playwright MCP (`mcp__playwright__*`) — the `playwright` server is headless and works without a display. Navigate to http://localhost:${FRONTEND_PORT}, log in as admin@windmill.dev / changeme.
IMPORTANT: Read docs/autonomous-mode.md before starting any work.
panes:
- id:agent
kind:agent
focus:true
- id:frontend
kind:command
split:right
workingDir:frontend
command:npm run generate-backend-client && npm run dev -- --port ${FRONTEND_PORT:-3000} --host 0.0.0.0
agentOnly:
runtime:host
yolo:true
envPassthrough:[]
systemPrompt:>
IMPORTANT: Read docs/autonomous-mode.md before starting any work.
panes:
- id:agent
kind:agent
focus:true
integrations:
github:
autoRemoveOnMerge:true
linkedRepos:
- repo:windmill-labs/windmill-ee-private
alias:ee-private
dir:../windmill-ee-private__worktrees
linear:
enabled:true
autoCreateWorktrees:true
watchTeams:[WIN,GIT]
oneshot:
systemPrompt:|
You are running in webmux ONESHOT mode.
# No interactive user
There is NO interactive user — nobody is watching the chat or will respond
to questions, approvals, or status checks. Any message asking the user to
review, approve, confirm, take a look, or "let you know" is wasted output:
it will not be answered.
# Your job
Take the task to its real conclusion without pausing:
1. Make the change.
2. Validate it (run the relevant tests, typecheck, build, or quick
manual check). For UI changes, drive the running frontend with
the Playwright MCP (`mcp__playwright__*`, headless) and confirm
the change works end-to-end before moving on.
3. Commit.
4. Push.
5. Open a pull request.
Only then are you done.
# Decisions
When something is ambiguous, pick the most reasonable default and proceed.
When you would normally ask "should I X or Y?", just pick one and continue
— note the choice in the PR description if it matters.
# PR readiness
Default to opening the PR as a draft. If you are highly confident in the
change — the scope is small and well-understood, validation passed
cleanly, and you would not change anything if a reviewer pushed back —
open the PR as ready-for-review directly (omit `--draft` when invoking
`gh pr create`, or call `gh pr ready <number>` after creation). Err on
the side of draft when validation was partial, the change touches
public APIs or shared infrastructure, or you made a non-obvious judgment
call.
# Ending your turn
Never end your turn with a question, a suggestion to "take a look", or a
request for approval. Stop only when the PR is open, or when you hit a
technical error you cannot recover from yourself (in which case clearly
Open-source platform for internal tools, workflows, API integrations, background jobs, and UIs. Rust backend + Svelte 5 frontend.
## Workflow
1.**Understand**: Before coding, explore the codebase (see Code Navigation below). Use `outline` to understand file structure, `body` to read specific symbols, `def`/`callers`/`callees` to trace code, `Grep` to find usages. Read `docs/` for domain context.
2.**Plan**: For non-trivial changes, use plan mode. For large features, break into reviewable stages
3.**Execute**: Follow coding patterns from skills (`rust-backend`, `svelte-frontend`)
4.**Validate**: After every change, run the appropriate checks per `docs/validation.md`
## Documentation
- **Validation**: `docs/validation.md` — what checks to run based on what you changed
- **Enterprise**: `docs/enterprise.md` — EE file conventions and PR workflow
- **Backend patterns**: use the `rust-backend` skill when writing Rust code
- **Frontend patterns**: use the `svelte-frontend` skill when writing Svelte code. Do NOT edit svelte files unless you have read that skill.
- **Frontend UUIDs**: do not call `crypto.randomUUID()` in frontend code. Import `randomUUID` from `$lib/utils/uuid` instead.
- **Code review**: review the current PR or branch against the shared review policy in `REVIEW.md` (severity triage, public-surface checklist, AGENTS.md compliance, test-coverage assessment). The skill at `.agents/skills/local-review/SKILL.md` orchestrates it. All three CLIs auto-discover the same SKILL — Claude reads `.claude/skills/` (symlinked to the canonical `.agents/skills/` file), Codex and Pi read `.agents/skills/` directly. Invoke with `/local-review` in Claude Code, `$local-review` (or `/skills` selector) in Codex, or `pi --skill local-review` / `/skill:local-review` in Pi.
- **Domain guides**: `.claude/skills/native-trigger/` and `frontend/tutorial-system-guide.mdc`
- **CLI commands**: when adding/modifying/removing a command, subcommand, option, or description in `cli/src/commands/`, run `python system_prompts/generate.py` to refresh `system_prompts/auto-generated/` and `cli/src/guidance/skills.gen.ts`. The CLI docs the agents use to operate `wmill` are derived from the source — stale generated files give agents the wrong flags.
## Dev Environment
- **Backend**: `cargo run` from `backend/` (API at http://localhost:8000)
- **Frontend**: `REMOTE=http://localhost:8000 npm run dev` from `frontend/` (port 3000+)
- **Instance settings**: navigate to `/#superadmin-settings`
- **Migrations**: use `cargo sqlx migrate add -r <name>` from `backend/` to create new migrations (never generate timestamps manually)
## Verifying Frontend Changes
After modifying frontend code, drive the running dev server with the **Playwright MCP** to verify the change in a real browser — don't claim a UI change works without exercising it.
Two MCP servers are registered in `.mcp.json`:
-`playwright` — headless Chromium, default for devboxes (no display required)
-`playwright-headed` — windowed Chromium, when a display is available
**One-time setup:** run `npx playwright install chromium` to download the browser binary (Playwright won't fetch it automatically on first use).
Typical flow:
1. Ensure backend (`cargo run`) and frontend (`REMOTE=http://localhost:8000 npm run dev`) are running
2.`mcp__playwright__browser_navigate` to the relevant page (login at `admin@windmill.dev` / `changeme`)
3.`mcp__playwright__browser_snapshot` to inspect the accessibility tree (preferred over screenshots for reading the DOM)
4.`mcp__playwright__browser_click` / `browser_fill_form` / `browser_type` to interact
5.`mcp__playwright__browser_take_screenshot` for visual confirmation
6.`mcp__playwright__browser_console_messages` / `browser_network_requests` to surface errors
**Attach the screenshots to the PR.** For any change under `frontend/`, embed screenshots of the affected UI in the PR body — the `pr` skill requires this and carries the upload recipe.
If you cannot exercise a UI change (no dev server, etc.), say so explicitly rather than claiming success.
## Banned Patterns
### `$bindable(default_value)` on optional props
Using `$bindable(default_value)` on props that can be `undefined` is **banned**. This pattern causes subtle bugs because the default value masks the `undefined` state.
**Bad:**
```svelte
let {my_prop=$bindable(default_value)}: {my_prop?: string} = $props()
```
**Correct alternatives:**
1.**Use `$derived` with nullish coalescing** — handle the potential `undefined` at the usage site:
let effective_value = $derived(my_prop ?? default_value)
```
2. **Create a `useMyPropState()` helper** — encapsulate the undefined-handling logic in a reusable function and call it higher in the component tree, so the child component always receives a defined value.
## Code Navigation
`wm-ts-nav` is an AST-aware code navigator. Use **wm-ts-nav** for structural queries — it skips comments/strings and understands symbol boundaries.
**MUST use `outline` before `Read`** on unfamiliar files — a 500-line file costs ~500 lines of context, while `outline` costs ~20. Then **MUST use `body "X"`** instead of reading a full file to see one function/struct. Use `Read` with offset/limit only when you need surrounding context that `body` doesn't capture.
- `refs "X" --caller` instead of reading files to find which function contains each reference
- `callers "X"` / `callees "X"` for call-graph questions
EE files (`*_ee.rs`, `*_ee.ts`, `*_ee.svelte`) are indexed — you can `outline`, `def`, `body`, `refs` etc. on them just like regular files.
```bash
NAV="sh wm-ts-nav/nav"
# Use --root backend for Rust, --root frontend/src for TS/Svelte
$NAV --root backend refs "X" --file handler.rs --caller # scoped refs with caller
$NAV --root backend callers "X" # who calls X?
$NAV --root backend callees "X" # what does X call?
```
**Limitations** — syntax-level analysis, no type inference. Use **Grep** instead when completeness matters (finding all usages, exhaustiveness checks):
- `refs`/`callers`/`callees` can't follow re-exports, glob imports, or different import paths to the same symbol
- Trait impls, macro-generated symbols (`sqlx::FromRow`), and namespace member access (`ns.X`) are invisible
- `callees` shows all identifiers in a function body, not just actual calls
## Core Principles
- **MUST `outline` before `Read`** on unfamiliar files — then `body` or `Read` with offset/limit for specifics
- Search for existing code to reuse before writing new code
- **Comments record constraints, not narration.** Write a comment only for what the code can't show: why a non-obvious approach is required, what breaks if it's "simplified" away. State each invariant once, at the place where someone would break it, in ≤4 lines. Don't describe what the next line does, don't repeat the same rationale at multiple sites, and don't address the PR reviewer (justifying a change belongs in the PR description, not the code). Describe the code as it is, never its drafting history: "we no longer do X", "unchanged behavior", "instead of the previous approach" are meaningless to a reader who never saw the earlier iteration — before finishing, reread your comments as if the current state is the only state that ever existed.
- **Never attribute work to a specific customer, account, or "requested by a customer" in repo-tracked content** (PR descriptions, commit messages, code comments, docs). Describe changes by their technical motivation instead.
Windmill is an open-source developer platform for building internal tools, workflows, API integrations, background jobs, workflows, and user interfaces. See @windmill-overview.mdc for full platform details.
## New Feature Implementation Guidelines
When implementing new features in Windmill, follow these best practices:
- **Clean Code First**: Write clean, readable, and maintainable code. Prioritize clarity over cleverness.
- **Avoid Duplication at All Costs**: Before writing new code, thoroughly search for existing implementations that can be reused or extended.
- **Adapt Existing Code**: Refactor and generalize existing code when necessary to avoid logic duplication. Extract common patterns into reusable utilities.
- **Follow Established Patterns**: Study existing code patterns in the codebase and maintain consistency with established conventions.
- **Single Responsibility**: Each function, component, and module should have a single, well-defined responsibility.
- **Incremental Implementation**: Break large features into smaller, reviewable chunks that can be implemented and tested incrementally.
## Language-Specific Guides
- Backend (Rust): see `backend/CLAUDE.md` and the `rust-backend` skill: `.claude/skills/rust-backend/SKILL.md`
- Frontend (Svelte 5): see `frontend/CLAUDE.md` and the `svelte-frontend` skill: `.claude/skills/svelte-frontend/SKILL.md`
## Dev Environment
- **Backend**: `cargo run` from `backend/` (API at http://localhost:8000)
- **Frontend**: `REMOTE=http://localhost:8000 npm run dev` from `frontend/`
- The `REMOTE` env var configures the Vite proxy target. Without it, API calls proxy to `https://app.windmill.dev` instead of the local backend.
- The dev server starts on port 3000 (or 3001+ if 3000 is in use).
- **Instance settings**: navigate to `/#superadmin-settings` (opens the drawer overlay)
## UI Testing with Playwright MCP
When testing the frontend with the Playwright MCP tools:
1.**Start servers**: Launch backend (`cargo run`) and frontend (`REMOTE=http://localhost:8000 npm run dev`) as background tasks
2.**Wait for readiness**: Backend takes ~60s to compile; check output for `health check completed`. Frontend starts in ~5s.
3.**Login flow**: Navigate to `/user/login`, click "Log in without third-party", fill email/password, submit
4.**Instance settings drawer**: Navigate to `/#superadmin-settings` to open the drawer directly
5.**Toggle components**: The YAML toggle uses a custom `<Toggle>` component where the checkbox is visually hidden (`sr-only`). Click the wrapper `<label>` element (the parent container with `cursor=pointer`), not the checkbox ref directly.
6.**Console errors to ignore**: `critical_alerts` 404s are expected on CE builds (EE-only endpoint). VSCode worker 404s are dev-mode artifacts.
## Code Validation (MUST DO)
After making code changes, you MUST run the appropriate checks and fix all errors before considering the work done:
- **Backend**: Run `cargo check` from the `backend/` directory. Only enable the feature flags needed for the code you changed — check `backend/Cargo.toml``[features]` section to identify which flags gate the crates/modules you modified. For example: `cargo check --features enterprise,parquet` if you only touched enterprise and parquet code.
- **Frontend**: Run `npm run check` from the `frontend/` directory.
## Querying the Database
`backend/summarized_schema.txt` provides a compact overview of all tables, columns, types, ENUMs, and foreign keys. Use it to quickly understand the data model and relationships. Note: this file is a simplified summary — it omits indexes, constraints details, and other metadata.
For exact table definitions (indexes, constraints, column defaults, etc.), query the database directly:
@@ -65,7 +65,7 @@ Setting up zsh autocomplete is also recommended — see the [workmux docs](https
Each worktree is assigned a **slot** that determines its ports:
| Slot | Backend | Frontend |
|------|---------|----------|
|---- | ------- | --------|
| 0 | 8000 | 3000 |
| 1 | 8010 | 3010 |
| 2 | 8020 | 3020 |
@@ -170,7 +170,8 @@ The setup is defined in `.workmux.yaml` at the repo root. Key sections:
- **`post_create`**: Runs `scripts/worktree-env` to generate `.env.local` with port assignments
- **`panes`**: Defines the tmux layout (agent, backend, frontend)
- **`files.copy`**: Copies `backend/.env` and `scripts/` into each worktree
- **`files.symlink`**: Symlinks `node_modules` and `.svelte-kit` to avoid reinstalling per worktree
The `post_create` hook also copies `frontend/node_modules` using `cp -a` (preserves `.bin/` symlinks that `cp -r` would dereference).
## Enterprise (EE) Code Access
@@ -191,6 +192,20 @@ sandbox:
This mounts both the main EE repo (used by the main worktree) and the EE worktrees directory (used by feature worktrees) into every sandbox container.
## Cargo Features
To build the backend with specific Cargo features (e.g., `enterprise`, `parquet`), pass them via `CARGO_FEATURES`. The backend pane reads this from `.env.local` and appends `--features <value>` to the `cargo watch` command.
**With `wm` (workmux):**
Set `CARGO_FEATURES` as an environment variable before creating the worktree:
You are reviewing a GitHub pull request for this repository. Apply this policy alongside your tool's output requirements.
## Read the project rules first
- Read `AGENTS.md` (repo root) and any `AGENTS.md` in directories touched by the diff before reviewing — they are the canonical contributor guide.
- Quote the exact rule from `AGENTS.md` when flagging a violation.
## Verdict (first line of the review)
Start every review with a single verdict line, before any other section (the only thing that may appear above the verdict is the optional `cc @<PR_AUTHOR>` ping described in "Pinging the author" below). Pick exactly one:
- **Good to merge** — no blocking issues and no nits worth surfacing.
- **Mergeable, but should ideally address nits: <short list>** — no blockers, but P2 findings that are worth a look. The list must name each nit briefly (e.g. "doc/code mismatch in `foo.rs`, half-finished `pub fn bar`").
- **Should address issues before merging: <short list>** — at least one P0 or P1 finding. The list must name each blocking issue briefly (e.g. "missing auth check on new `/api/x` handler, SQL injection in `build_query`").
The names in the list must match findings detailed later in the review. If you list a nit or issue here, it must appear with full context in the body. Do not invent items that aren't in the body, and do not bury blockers in the body without surfacing them in the verdict.
## Pinging the author
If the prompt context provides a `PR AUTHOR` (GitHub login) and the verdict is NOT "Good to merge" (i.e. it is "Mergeable, but should ideally address nits: ..." or "Should address issues before merging: ..."), prepend a single line `cc @<PR_AUTHOR>` to the top-level review comment, above the verdict line. This pings the author so they get a notification that there are items to address. Skip the ping entirely when the verdict is "Good to merge" — there is nothing for the author to act on. Do not add the ping to inline comments; the top-level summary comment is the only place it belongs.
## Review policy
- Only report issues you are confident are real and introduced by this pull request.
- Focus on bugs, security problems, performance, and clear `AGENTS.md` violations.
- Do not report style nits, speculative concerns, pre-existing issues, or anything a normal linter / typechecker would obviously catch.
- Self-validate each finding before posting: "is this definitely a real issue?" If uncertain, discard it.
- Read additional files only when the diff is not enough to validate a finding.
- Do not modify any files.
## Severity triage
Tag each finding with a severity. Always report P0 and P1. Report P2 only when the diff invites it (a new `pub fn`, a new module, a new exported component, a meaningful refactor).
- **P0** — RCE, auth bypass, data loss, secrets in code, SQL injection, path traversal, broken auth on a public surface.
- **P1** — significant bug, missing auth/authorization check on a new public surface, blocking I/O on a likely async path, race condition, missing input validation on caller-controlled parameters, observable performance regression.
- **P2** — wrong module placement, doc/code mismatch, half-finished public abstractions (`pub fn` + `#[allow(dead_code)]` + `TODO`), `AGENTS.md` style violations, naming that contradicts the function's behavior.
## Checklist for new public surfaces
For any new `pub fn` / `pub async fn` / exported Svelte component / exported prop introduced by this PR, verify:
- (a) auth/authorization expectations are documented in the doc comment OR enforced in the function body. A new `pub fn` that touches workspace data, secrets, files, or processes without an auth check or documented "caller MUST verify" contract is a P1.
- (b) the function is placed in a module whose stated purpose matches what it does. Check the module-level doc comment (`//!`) — a config-file reader inside `external_ip.rs` is a P2.
- (c) it is not half-finished. `pub fn` + `#[allow(dead_code)]` + a `TODO` is a smell that says the function should land together with its caller, not separately. Cite the relevant `AGENTS.md` rule.
- (d) input validation defends against injection / traversal / overflow / NUL bytes at every parameter that may be caller-controlled.
## Test coverage assessment
End your review with a short "Test coverage" section calibrated to the layers actually changed by the diff. Skip categories the PR does not touch.
- **Backend** (Rust under `backend/`) — expect Rust unit tests for new logic. For new or modified API handlers, worker steps, queue/cron behavior, or DB access, also expect or note the absence of integration tests. Pure-refactor backend PRs don't need new tests if existing tests cover the surface.
- **Frontend** (Svelte / TS under `frontend/`) — the codebase does not generally test Svelte components, so do not ask for component tests. Only flag missing tests for new pure-logic utilities (the kind of file that already has a sibling `*.test.ts`, e.g. `flowDiff`, `previousResults`, copilot logic).
- **CI / workflows / docs / config-only** — no automated tests expected; say so explicitly so the reader knows you considered it.
Then state what manual verification, if any, is still needed before merge:
- Describe each manual scenario as a short paragraph (not a numbered list): what page / action / input, and what observable outcome confirms correctness.
- If the diff has no in-app surface to exercise (purely backend internals, CI, docs, or refactor), say that plainly.
## Additional reviewer instructions
If the prompt or context includes an "Additional reviewer instructions" section, treat it as extra guidance from the human who triggered this review and follow it.
## Prior PR discussion
If the prompt or context includes a "Prior PR discussion" section, this PR has already received review activity. Look for your own previous comment, take it into account, focus on what changed in the latest commits, and do not repeat findings the human already pushed back on or addressed.
This folder contains black-box benchmark cases for:
-`flow`
-`app`
-`script`
-`cli`
-`global`
The goal is to test the current production prompts and guidance with realistic user requests, not to test one exact implementation shape.
## Core rules
1. Write prompts like a real user request.
2. Prefer behavior, inputs, constraints, and outcomes over internal implementation details.
3. Keep deterministic validation narrow and hard.
4. Put semantic expectations in `judgeChecklist`.
5. Use `expected` fixtures only when exact structure really matters.
## Prompt writing
Prompts should sound like something a user would naturally ask.
Good:
- "Create a flow that routes support requests based on customer tier."
- "Add a reset button that sets the counter back to 0."
- "Create a flow that reuses the existing greeting script instead of duplicating the logic."
Bad:
- "Use `branchone` with 3 branches and a default branch."
- "Create a `rawscript` step with this exact topology."
- "This is a benchmark harness."
Do not write prompts as if the user knows Windmill internals unless the case is explicitly testing a power-user workflow.
## Flow-specific rules
This is the main principle you asked for:
- flow prompts should read like requests from a user who does not know the product internals
- the user should ask for behavior, not for `branchone`, `branchall`, `rawscript`, `preprocessor_module`, `failure_module`, exact graph topology, or other internal constructs
That means:
- creation cases should describe the business behavior and expected result
- modification cases may mention existing step names, because the user can see the current flow
- only mention special Windmill constructs when the case is explicitly about those constructs
Examples:
- acceptable creation prompt:
"Create a purchase approval flow that pauses for approval and asks the approver for a comment."
- avoid:
"Create a suspend step with one required event and a resume form."
For flow cases, do not fail a case just because the model chose a different valid topology.
## App-specific rules
App prompts should focus on user-visible behavior:
- what the UI should let the user do
- what should persist
- what backend behavior is needed
Avoid prompting in terms of React structure, component names, or implementation unless the case is specifically about editing an existing app.
## CLI-specific rules
CLI prompts can be more explicit about paths and file names because real CLI users often do specify them.
Still, avoid benchmark phrasing. The prompt should read like a repo task, not a harness instruction.
When relevant, ask the assistant to tell the user which `wmill` commands to run next. That is part of the benchmarked behavior.
## Global-specific rules
Global prompts should exercise workspace-level drafting behavior:
- inspecting existing scripts, flows, apps, schedules, triggers, resources, and variables when relevant
- writing AI drafts rather than saving or deploying by default
- producing coherent multi-artifact changes when the request crosses artifact boundaries
Keep deterministic validation focused on the draft contract: required draft type/path, required content snippets, forbidden draft paths, and forbidden mutating tools such as deploy/delete unless the case explicitly asks for them.
Datatable cases should set `skipJudge: true` and validate through tool-use
(`requiredToolsUsed` / `forbiddenToolsUsed`) and SQL-argument assertions
(`toolCallArgs` with `stringIncludesAnyOf`, e.g. `['select']`, `['create table']`,
`['update', 'insert into']`). Two reasons the judge is unreliable here:
-`list_datatables`, `get_datatable_table_schema`, and `exec_datatable_sql`
produce no drafts, and the global judge only sees the drafts artifact — it
scores a no-draft conversational answer as empty (same as the
`askUserQuestion` cases).
- Even a case that *does* produce a draft (a script reading the data table via
`wmill.datatable()` at runtime) is mis-judged: the judge has no datatable SDK
reference and penalizes correct `wmill.datatable()` usage as wrong. Verify the
Small benchmark runner for the Windmill AI generation modes:
-`cli`
-`flow`
-`script`
-`app`
-`global`
The benchmark always tests the current production prompts, tools, and guidance in this checkout.
Each attempt runs:
1. the real production path
2. deterministic validation
3. LLM judging
## Install
```bash
cd ai_evals
bun install
```
Frontend modes also require frontend dependencies:
```bash
cd frontend
bun install
```
## Commands
List model aliases:
```bash
cd ai_evals
bun run cli -- models
```
List cases:
```bash
cd ai_evals
bun run cli -- cases
bun run cli -- cases flow
```
Run benchmarks:
```bash
cd ai_evals
bun run cli -- run flow
bun run cli -- run flow flow-test4-order-processing-loop --model opus
bun run cli -- run flow flow-test0-sum-two-numbers --models haiku,opus,4o
bun run cli -- run flow flow-test0-sum-two-numbers --runs 3 --verbose
bun run cli -- run flow --record
GEMINI_API_KEY=... bun run cli -- run app app-test1-counter-create --model gemini-3-flash-preview
WMILL_AI_EVAL_BACKEND_URL=http://127.0.0.1:8000 bun run cli -- run flow --backend-validation preview
bun run cli -- run global global-test1-script-create
bun run cli -- run cli bun-hello-script
```
Public CLI surface:
-`models`
-`cases [mode]`
-`run <mode> [caseIds...]`
`run` options:
-`--runs <n>`: repeat each case `n` times
-`--output <path>`: custom result JSON path
-`--model <alias>`: choose the model under test
-`--models <a,b,c>`: run the same cases sequentially against several model aliases
-`--verbose`: stream assistant output for frontend runs
-`--skip-judge`: skip LLM judge scoring for the run
-`--execution-only`: only require the model/proxy/frontend loop to complete; skip validators, tool expectations, backend artifact validation, and judge scoring
-`--record`: append a compact tracked summary line to `ai_evals/history/<mode>.jsonl` for full-suite runs only
-`--backend-validation <mode>`: optional backend smoke validation (`off` or `preview`) for `script` and `flow` evals
## Models
Use `bun run cli -- models` to see the current aliases.
Today:
-`haiku`
-`sonnet`
-`opus`
-`4o`
-`gpt-5.5`
-`gemini-3-flash-preview`
-`gemini-3.1-pro-preview`
-`deepseek-v4-flash`
-`deepseek-v4-pro`
Notes:
- the command also prints accepted alias spellings such as `gpt-4o`, `gpt-55`, `claude-opus-4.6`, and `claude-haiku-4.5`
- frontend modes (`flow`, `script`, `app`, `global`) can use Anthropic, OpenAI, Gemini, and DeepSeek-backed aliases
-`cli` mode always uses the Anthropic agent SDK, so only Anthropic aliases are valid there
- the judge model is separate and currently defaults to `claude-sonnet-4-6`; use `--skip-judge` for deterministic-only runs
## Case Format
Cases live in one YAML file per mode under `ai_evals/cases/`.
Minimal shape:
```yaml
- id:flow-test0-sum-two-numbers
prompt:|-
Create a flow that takes two numbers, `a` and `b`, and returns their sum.
initial:ai_evals/fixtures/...
expected:ai_evals/fixtures/...
```
Optional fields:
-`initial`: starting state fixture
-`expected`: expected artifact fixture
-`validate`: extra deterministic validation rules
-`runtime.backendPreview`: optional real backend preview config for smoke validation
For `flow` mode, `validate` can express requirements such as:
- accepted input schema shapes
- required `results.*` reference validity
- required module/code/input characteristics
For `app` mode, `validate` can express narrow hard requirements such as:
- required frontend file paths or backend runnable keys
- minimum backend runnable counts
- required backend runnable types
- minimum datatable / datatable-table counts
- specific required datatable tables
For `global` mode, `validate` can express draft-level requirements such as:
- required draft type/path/language
- required or forbidden snippets in draft values
- required or forbidden draft counts
- forbidden draft paths
Global initial fixtures can also seed `liveEditorDrafts` with `type`,
`storagePath`, `effectivePath`, and `value` fields. These drafts emulate the
currently open script, flow, or raw app editor so cases can test prompts that
refer to "this" or the "current" item.
Global (and flow) initial fixtures can seed `workspace.datatables` so the
`list_datatables`, `get_datatable_table_schema`, and `exec_datatable_sql` tools
SQL runs through a small in-memory engine (`datatableSqlEngine.ts`), not a real
database. Writes are **stateful within a case**: `CREATE`/`DROP`/`INSERT`/`UPDATE`/
`DELETE` mutate the seeded datatable in place, so a later `list_datatables`,
`get_datatable_table_schema`, `SELECT`, or `information_schema` query reflects them
— this is what stops a model from looping when it re-queries to verify a write.
The engine is best-effort: `SELECT` returns all rows of the referenced (or first)
table with no WHERE filtering/projection/joins, `WHERE` on UPDATE/DELETE supports
`col = value` predicates joined by `AND`, and anything unparseable is a no-op
success. So validate datatable cases through tool-use and SQL-argument assertions
(`requiredToolsUsed`, `stringIncludesAnyOf`) — not through exact returned row
values. An empty/absent `datatables` seed makes `list_datatables` return `[]`,
which is what the "no datatable configured" blocking cases rely on.
Set `WMILL_AI_EVAL_DISABLE_ACTIVE_EDITOR_CONTEXT=1` to run those cases with
the old behavior where the live editor is only discoverable through
`list_workspace_items`.
App fixtures can also include an optional `datatables.json` file at the fixture root.
For `flow` mode, an `initial` fixture can also include a benchmark workspace catalog of
existing scripts and flows. That lets the real `search_workspace` and
`get_runnable_details` tools discover reusable workspace runnables during evals.
If `--backend-validation preview` is enabled:
-`script` evals run a real backend script preview in an isolated temp workspace
-`flow` evals run a real backend flow preview only for cases that define `runtime.backendPreview`
-`flow` cases with `initial.workspace` fixtures seed those scripts and flows into the preview workspace before preview
- when `WMILL_AI_EVAL_BACKEND_WORKSPACE` is set, `ai_evals` creates or reuses that workspace as a dedicated test workspace, clears managed eval assets under `f/evals/*` before each preview run, and then reseeds the current case fixtures
-`WMILL_AI_EVAL_BACKEND_WORKSPACE=integration-tests` to reuse an existing workspace on CE installs with low workspace limits
Frontend modes require a reachable Windmill backend and send model requests through the workspace AI proxy at `/api/w/{workspace}/ai/proxy`. At startup, `ai_evals` checks the resolved backend URL and fails early with setup guidance if the backend cannot be reached or login fails.
For frontend modes:
-`ai_evals` creates a temporary backend workspace, or creates/reuses `WMILL_AI_EVAL_BACKEND_WORKSPACE` when it is set
- it upserts a provider resource under `f/evals/ai/<provider>`
- frontend requests go through `/api/w/{workspace}/ai/proxy`
## Results And Artifacts
Every run writes:
- a summary JSON under `ai_evals/results/`
- generated artifacts in a sibling directory
If `--record` is used, the CLI also appends one compact JSON line to:
-`ai_evals/history/flow.jsonl`
-`ai_evals/history/script.jsonl`
-`ai_evals/history/app.jsonl`
-`ai_evals/history/global.jsonl`
-`ai_evals/history/cli.jsonl`
Each recorded line contains:
- run metadata (`createdAt`, `gitSha`, `mode`, `runModel`, `judgeModel`)
- suite totals (`caseCount`, `attemptCount`, `passedAttempts`, `passRate`, `averageDurationMs`, `averagePassedDurationMs`, `averageJudgeScore`)
- average token usage (`averageTokenUsagePerAttempt`, `averageTokenUsagePerPassedAttempt`)
expect(applyDatatableSql(dt,'GRANT SELECT ON orders TO someone').rows).toEqual([])
})
})
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.