* feat: auto-build binaries to object storage on deployment
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: queue the auto-build from pre-locked deploys and off the lock slot
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: materialize companion modules before a deploy-time build
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep a build job from stamping lock_error_logs on a healthy script
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: de-flake test_flow_lock_all and surface the lock error it hides
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: trim drafting history from the flow-lock fixture comments
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop a binary build from restarting dedicated workers
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the build-job marker off the agent wire and out of user args
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop websockets resurrecting a reclaimed dev server
* docs: condense the websocket invariant comment
* test: stub fetch suite-wide so waking cannot hit a real dev server
* fix: let websockets join an in-flight start
* fix: close SSRF bypasses in git URL validation (DNS + redirects)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: name the remedy when a git probe stops at a redirect
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: retry the .git form when a probe stops at a same-host redirect
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the .git retry on the validated host for pathless URLs
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: add dev server supervisor and dev-only polling dormancy
* fix: address review findings in dev supervisor
* fix: support https mode and bound the idle reaper in dev supervisor
* fix: persist dormancy install guard and hold the reaper during startup
* chore: run worktree frontends under the dev supervisor
* fix: keep app websockets working and reap children on sighup
* feat: add EXIT_AFTER_N_JOBS worker mode for environment cleanup
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address review findings on the EXIT_AFTER_N_JOBS worker mode
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address round-2 review findings on EXIT_AFTER_N_JOBS
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address round-3 review findings on EXIT_AFTER_N_JOBS
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: bound WORKER_SUFFIX length and document the same-worker drain
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: validate the assembled worker name length
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: bound go compilation memory with GOMEMLIMIT
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: bound the whole go build tree, not each toolchain process
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the go build memlimit and parallelism atomic
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: log the go limits actually installed and stop serializing small workers
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: make go build parallelism authoritative over persisted GOFLAGS
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: canonicalize the go build -p value and floor the module-step budget
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: parse GOMAXPROCS for -p the way the go runtime does
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: read GOMAXPROCS with go's own grammar and report limits neutrally
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: derive go build parallelism from the cgroup quota over its own period
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep go's minimum build parallelism under sub-CPU quotas
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the windows 1CU cap out of go's two-compiler floor
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: record that a worker runs one job at a time
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: scope the one-job-at-a-time rule away from native workers
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: home search matches each term instead of the whole query verbatim
* docs: state the search term cap and drop unreachable test cases
* fix: treat a term-less search as no filter and trim the comment
* fix: a term-less search matches nothing instead of the whole page
* feat: match the homepage fuzzy search exactly in the runnables endpoint
* docs: say apostrophes stay in terms; test summary-less and draft rows
* docs: separate an empty search from one holding no terms
* docs: state that terms split on ASCII alphanumerics only
* fix: escape and validate custom env var names in the nativets prologue
Custom workspace environment variable names were spliced verbatim into the
generated NativeTS/Bun JS prologue (both the `const {name}` binding and the
`process.env['{name}']` assignment), while only the value was escaped. A
non-identifier name could therefore alter the generated program.
- Add `escape_js_single_quoted` / `is_valid_js_identifier` helpers.
- worker.rs and bun_executor.rs: escape the name as a string literal, and only
emit the `const {name}` binding for valid identifiers.
- set_environment_variable: reject non-identifier names on write (deletion stays
unrestricted so existing rows remain removable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: address review — reserved-word const gate, grandfathered-name editability
- Gate the `const {name}` prologue binding on `can_bind_as_prologue_const`, which
additionally excludes JS reserved words and the prologue's own bindings
(`process`, `BASE_URL`, `BASE_INTERNAL_URL`); such names would otherwise emit a
SyntaxError that breaks every NativeTS run. They are still exposed via
`process.env['{name}']`.
- set_environment_variable: only enforce the identifier check for names that don't
already exist, so editing the value of a pre-existing non-identifier name (the
edit UI resubmits the name) isn't rejected with no in-product fix.
- Document the name constraint on the endpoint in openapi.yaml.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: exclude eval/arguments from const gate; skip existence query on valid names
- Strict-mode ES modules forbid `eval` and `arguments` as binding names, so add
them to the non-bindable set — otherwise an env var named `eval`/`arguments`
emits `const eval = ...`, a SyntaxError that breaks every NativeTS run.
- set_environment_variable: run the existence check only when the name isn't a
valid identifier, so the common (valid-name) path skips the extra query; trim
the rationale comment.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: allow `async` as a prologue const binding; note reserved-bindings coupling
`async` is a contextual keyword, not a reserved word — `const async = ...` is
valid, so it needn't be excluded from the const binding. Also cross-reference the
prologue head from PROLOGUE_RESERVED_BINDINGS so the two stay in sync.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: authorize GET /concurrency_groups/{job_id}/key per job
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: answer 404 for an inaccessible and an unknown job alike
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(sessions): persist artifact version selection in preview tabs
The artifact viewer's version pin was component-local state, so picking an
older version from the history dropdown was lost on reload. It now rides on
the preview tab's URL (`artifact:<id>?v=<n>#<name>`), which is persisted with
the tab, so a reload lands the reader back on the version they were reading.
Omitting a version means "leave the reader where they are", not "show the
latest". Every artifact tool re-opens the document it just wrote, so an
omitted version that cleared the pin would yank a reader out of the version
they chose on every single edit. That rule lives in keptVersion(), which
targetUrl() applies to every path that re-points a tab, so open() and
navigate() cannot disagree about it — the breadcrumb picker opens highlighting
the artifact the active tab already shows, and re-picking it must not double
as a reset to latest. A pin belongs to a (tab, artifact) pair, so a tab
re-pointed at a different document carries nothing over, and a new tab starts
unpinned. Moving off a pin is the reader's own action, through the version
dropdown, "Back to latest", or the new pinArtifactVersion(). Since the pin is
part of the tab model, get_preview_status now reports it, so the assistant can
tell that the reader is not looking at what it just wrote.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(sessions): bound a stamped artifact version to a safe integer
Number.isInteger(1e21) is true, but interpolating it yields `?v=1e+21` while
parseArtifactRoute matches digits only, so artifactUrl could stamp a url that
reads back as null — the one outcome the guard exists to prevent, and one that
would persist with the tab. Safe integers always interpolate in decimal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(artifacts): tell a failed version read apart from a missing version
getArtifactVersion swallowed a rejected read and returned undefined, so a
transient IndexedDB failure was indistinguishable from a pruned snapshot. Both
its callers act on that distinction, and both acted wrongly: the artifact
viewer clears the reader's pinned version on absence — now that the pin is
persisted with the tab, clearing it destroys it — and read_artifact tells the
model the version is gone and to call list_artifact_versions.
It now rejects instead. The store still answers for the current version, which
it holds in memory and can serve without the DB; anything older propagates, the
viewer keeps the pin and leaves the document on screen, and read_artifact
reports a read it could not make rather than a version that does not exist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix: clear orphaned usr_to_group rows on service account creation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: pin service account creation over orphaned usr_to_group rows
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 60c20e686cead73ff075512b15c6e2d6232beca6
This commit updates the EE repository reference after PR #723 was merged in windmill-ee-private.
Previous ee-repo-ref: 5c2c553f960abcd7988fdac8830dd36c066160ad
New ee-repo-ref: 60c20e686cead73ff075512b15c6e2d6232beca6
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(frontend): use the Password component on the login and reset-password forms
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): submit auth forms once per Enter keypress
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): conceal revealed password before submitting auth forms
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop the AI chat destroying secret variables on edit
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: clear stale staged secret values and state the draft-staging rule
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: condense the pending-secret invariant to its field
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: refuse empty and oauth-managed secret values, keep drawer-staged ones in the draft
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: resolve a variable deploy's secret from one draft snapshot
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: make the variable draft the single source of a staged secret
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: drop stale in-memory secret invariants from comments and the eval
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop null account/expires_at leaking into variable drafts and diffs
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: report when a variable deploy leaves the secret value unchanged
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: scope the variable-value readability claims to the chat
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: correct the secret-draft invariant in the diff masking comment
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: record why a non-secret value is resent on a partial update
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop "Load secret value" discarding a staged secret
The audit-logged load writes the deployed secret into the draft row the
variable drawer shares with the AI chat, so offering it while that row
already stages a value silently replaces it — and the deploy that follows
carries the old value with no sign the staged one was lost.
The gate that hid the action already existed but keyed on
`isEncryptedDraftValue`, which only holds once a draft has round-tripped
through the server. A value staged in the same tab is still plaintext, so
it slipped through. Key on "anything staged" instead; clearing stays
explicit via Reset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: extend the variable draft's empty-value sentinel past secrets
Two gaps in the chat's variable write path, both from treating "the draft
cannot carry this value" as meaning only "the value is secret".
`variableToDraftState` drops the value of an OAuth-managed variable so a
refreshed live token is never pinned into a draft, leaving '' behind. The
deploy body resent that '' verbatim for a non-secret one, wiping the token
the refresh flow owns. The sentinel now covers every value the draft is not
allowed to hold, which also removes the divergence from
`VariableEditor.save` and the shared deployer.
Making a variable secret when it holds no value produced a secret draft
staging '', a deploy body with no `value`, and the backend's "cannot change
is_secret without updating value too" — the sibling create path already
answers that case with guidance, so answer it here too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: gate the Secret toggle's secret load on the staged value too
The toggle calls `onLoadSecret` on every change so an is_secret flip has a
value to send, but that load overwrites the shared draft row — the same
discard the button gate just closed, reached by a different control.
It now loads only when the row stages nothing, which is exactly when the
flip needs a value fetched. With a value already staged there is one to
send, and it is the one the user or the chat put there.
Blocking the load costs the side effect that used to mask a worse bug: for
a deployed variable, the load replaced an `$encrypted:` marker with real
plaintext before save. Without it, un-securing a marker would store the
marker string as the value, since the deploy endpoints only decrypt it while
is_secret stays true. So the toggle is disabled outright while a marker is
staged — Reset first. That closes the marker case for draft-only variables
as well, where no load could ever have masked it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
create_schedule opened the RLS transaction (user_db.begin) first, then ran
reads that deliberately use the non-RLS `db` pool — fork-ness and
permissioned_as/email resolution — while holding it. Acquiring a second pooled
connection while the tx holds one self-deadlocks on a single-connection pool
(embedded Postgres, PgBouncer statement mode, any max_connections=1 setup): the
read blocks on the sqlx acquire timeout, then errors.
Move those reads (and the ScheduleType::from_str validation) above
user_db.begin(). They don't depend on the tx and bypass RLS by design, so the
result is semantically identical; the RLS transaction is simply opened later and
held for less time. Same class of fix as #9970 (migration bootstrap on the
migrator's held connection).
Note: sibling paths keep the same latent pattern on branches this change does
not touch (push_scheduled_job reads the pool under the tx for flow schedules;
edit_schedule/set_enabled for cross-user permissioned_as) — a possible follow-up.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* test: wait for the app dependency job before pulling in repro_diffname
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: reuse waitForDeploymentJobs and assert pulled lock files
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: condense the dependency-job wait comment
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: expose every runs filter on the open_page chat tool
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: reject runs filters the page would silently ignore
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: normalize runs list filters and refuse combinations the page drops
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: validate the full folder-name contract and pin evals to one call
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: refuse queue statuses the concurrency view cannot filter on
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: avoid content shift on home page load and in the script editor logs pane
The tutorial banner rendered by default and was removed once an API round-trip
resolved that it should not show, jumping everything below it up by 58px on
every home page load. It now caches the last resolved state in localStorage and
paints that first, so the first frame already matches what the sync concludes; a
device with nothing cached stays hidden until the sync answers.
The logs header spinner was an unsized lucide icon (24px) where the settled
state renders a 12px Timer, so the row grew 7px while a job was queued and
shrank back when it started, shoving the log body down and up again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the tutorial banner hidden when dismissed mid-sync
The banner is interactive while the initial tutorial-progress request is still
in flight, so a dismiss or a skip can land before the sync resolves. The
continuation then overwrote the user's choice and brought the banner back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: pin the result placeholder row height across the spinner swap
Sizing the spinner to the font size still left it 6px short of the text-sm line
box it replaces, so the row contracted instead of growing. Pin the height on the
container so it holds in both states and tracks the root font size.
Also assign state before persisting it, and collapse the duplicated rationale
above the banner cache.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop the test panel splitpanes resting one header too tall
The panes carried `!max-h-[calc(100%-{...}px)]`, but the arbitrary value is
built by string interpolation so Tailwind never emitted a rule for it: the
class was inert and the computed max-height was `none`. The panes then took
their 100% height, ignoring the header row above them, and overflowed the
column by exactly the header. Flex only applied the shrink transiently, so a
reflow during a run snapped the whole logs & result region up ~12px and back.
min-h-0 lets flex size the panes to the space that is actually left, which is
what the clamp was reaching for and is correct for the debug and bottom layouts
too, without their hardcoded 83/43/0 pixel guesses.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: bound postgres result collection so it cannot OOM the worker
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: render the sql result limit exactly so the error can be set verbatim
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: point the fraction rationale at the renderer that still emits them
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf: stop re-parsing every collected row to rebuild it as a RawValue
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* style: drop a dangling doc line and an unrelated rustfmt reflow
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: bound duckdb result collection so an oversized result cannot OOM the worker
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the duckdb cap a worker-survival limit rather than a cloud product one
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: refuse an oversized blob before it expands to one json value per byte
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: share one expansion budget across a row's values, nested ones included
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: bound the row's own serialization so escaping cannot outgrow the budget
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: charge a json column before parsing it into a value tree
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: trim the json budget rationale and name what the budget does not cover
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: gate the sql result size limit on the duckdb feature
Its only consumer is the duckdb executor, so the minimal build compiled it
as dead code and failed under -D warnings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>