* 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>
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>
- 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>
* 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: 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>
* 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>
* [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>
* [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>
* 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>
* 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>