* fix: bind /etc resolver files read-only in nsjail sandboxes
* docs(nsjail): explain why per-file /etc resolver binds are load-bearing
The explicit /etc/hosts, /etc/resolv.conf and /etc/hostname binds look
like removable duplication of the read-only /etc bind above them. They
are not: on Kubernetes those files are separate kubelet bind-mounts on
top of /etc and nsjail's read-only remount is non-recursive, so without
these shadow binds they stay writable and a job can persist cross-tenant
DNS poisoning for the pod lifetime. Comment guards against a future
"dedup cleanup" silently reintroducing the vulnerability.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(nsjail): shorten the load-bearing-bind comment to 3 lines
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix: atomic bundle cache writes to prevent parallel cold-load race
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: trust-but-replace in atomic_publish_dir to never trust a stale partial cache dir
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: simplify atomic_publish_dir and add content-addressed rename-failure fallback
Revert the destroy-then-recreate dir swap (introduced concurrent-publisher
edge cases: spurious Err under a real herd, EACCES masking a stale partial)
back to a single atomic rename. Add the content-addressed exists-fallback to
atomic_write_file_bytes/atomic_copy_file so the loser of a publish race (and
Windows, where rename cannot replace an open/existing destination) treats the
already-published identical cache as success instead of failing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: aggregate wait time should target the true root job, not flow_innermost_root_job
* refactor: reuse get_root_job_id helper for wait-time aggregation
Instead of duplicating the root_job → flow_innermost_root_job →
parent_job fallback chain inline, call the existing get_root_job_id()
helper (the same one used by push_next_flow_job) and filter out the
self-id case so standalone scripts still skip aggregate insertion.
Behaviorally identical, single source of truth.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: windmill-internal-app[bot] <1429786+windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(python): preserve strings containing Infinity/NaN in result JSON
* test(python): add sanity checks for Infinity/NaN in results
* refactor(python): use string-aware regex callback for single-pass cleanup
* refactor(python): compact regex + handle backslash-escape parity
* perf(python): short-circuit cleanup when no Infinity/NaN/NUL in result
* fix(bun): pass --preserve-symlinks on unbundled execution
Bun 1.2/1.3 moved its global package cache to a content-addressed
layout and the installer now creates a single directory symlink from
node_modules/<pkg> to the cache entry. Without --preserve-symlinks,
Bun resolves modules from each file's realpath, so any require/import
inside an installed package walks up from cache_nomount/bun/... and
never finds the sibling deps living under <job_dir>/node_modules/.
This manifested as e.g.
ENOENT while resolving package 'zod/v3' from
'/tmp/windmill/cache_nomount/bun/@langchain/core@1.1.44@@@1/dist/...'
on //nobundling scripts that pull @langchain/core, even though zod is
correctly installed alongside it in node_modules.
The bundled execution path already had --preserve-symlinks since #4132
(needed because we symlink the cached bundle file into the job dir).
The unbundled path didn't, because at the time Bun installed via per-
file hardlinks and the realpath of node_modules entries was the job
dir itself. The Bun installer's layout change made the flag necessary
on the unbundled path as well.
Add the flag to all three unbundled `bun run` invocations:
- nsjail unbundled path
- non-nsjail unbundled path
- dedicated worker (always unbundled)
This also fixes a latent bug on the first run of any bun script that
imports a package whose internals reference siblings (the build_cache
path runs unbundled this round while it builds the bundle for next
time).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(bun): regression test for nobundling + transitive require resolution
Adds an integration test that mirrors the original failure: a //nobundling
script importing @langchain/core, which (in its CJS internals) does
require('zod/v3'). Before --preserve-symlinks was added to the unbundled
bun run invocations, this failed with:
ENOENT while resolving package 'zod/v3' from
'.../cache_nomount/bun/@langchain/core@<ver>@@@1/dist/runnables/base.js'
The test covers the non-nsjail unbundled path. Reproducibility of the
pre-fix failure depends on Bun's installer choosing the directory-symlink
layout for the node_modules entry (the default on Bun 1.2/1.3+ with the
new content-addressed global cache that produced the user's error).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: refine windmill ai refactor plan
* refactor: move ai sse plumbing to windmill-ai
* refactor: remove ai re-export shims
* fix: update ee ai memory ref
* chore: update ee-repo-ref to d3bc7fa85195b46b7a38d43c2f806520bf8b5454
This commit updates the EE repository reference after PR #560 was merged in windmill-ee-private.
Previous ee-repo-ref: ff35bf7cc198e13884b33654e1d6dbd8a8b314d3
New ee-repo-ref: d3bc7fa85195b46b7a38d43c2f806520bf8b5454
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
The Python per-package dependency cache could persist an incomplete wheel
extraction with `.valid.windmill` set, then propagate that broken artifact
to every worker through the object store. Customer hit this on
argon2-cffi==25.1.0 (missing argon2/_utils.py), and previously on
botocore/httpx (truncated tars). Symptom is a runtime ImportError that
looks like a missing dependency declaration rather than a Windmill bug.
Three changes that together stop the propagation:
1. After `pull_from_tar`, parse the wheel's `<dist-info>/RECORD` and
confirm every listed path exists on disk before writing
`.valid.windmill`. On failure, wipe the directory and fall through
to a fresh local install — the next install also self-heals the
broken object-store entry by pushing a fresh tar.
2. After `uv pip install` succeeds, run the same RECORD check before
queuing the piptar upload or writing `.valid.windmill`. A bad install
never becomes the source of a broken tar in the object store.
3. Finalize the tar (`drop(tar.into_inner()?)`) before reading its bytes
for upload, so we never push an unfinalized archive (no end-of-archive
marker) to the object store.
Verified with a 60-package end-to-end integration test (first-fill →
clear-local-cache → re-pull-from-objectstore → corrupt-objectstore-tar
→ detect-and-self-heal). All 27 packages on the live test pulled cleanly,
and the deliberately corrupted argon2-cffi tar was caught with the exact
expected log line ("wheel RECORD lists files missing on disk: argon2/_utils.py")
and replaced with a fresh tar.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: parse windmill_failure field in job result to tag run as failure
* feat: preserve top-level fields when windmill_failure tags a run as failure
* fix: address review findings on windmill_manual_failure
* refactor: rename windmill_manual_failure to wm_failure and add wm_* aliases
* fix: prefer injected ManualFailure error over sibling name/message in OTel
* fix(flows): inherit flow_env in sub-flow predicates
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(flows): align flow_env lookup with get_root_job_id and tighten gate
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(flows): drop recursive CTE, root_job propagation suffices
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(flows): walk via flow_innermost_root_job to respect imported-flow scope
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(flows): remove flow_env API endpoint, dead code from deno_core era
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(flows): don't bubble error when continue_on_error is on the last step
When the last step of a flow (or branch/forloop) failed with continue_on_error
or skip_failures enabled, should_continue_flow resolved to false (because the
flow was at its last step), and the flow was completed with success=false.
This made parent flows / subflows treat the run as a failure even though the
user explicitly asked to continue past errors.
Detect this case and set success=true so the failure is captured in the
result but not propagated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: explain why success is overridden post should_continue_flow
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: distinguish job-already-completed from execution failure on OTLP span
handle_queued_job's bool return type conflated two distinct Ok(false)
cases: a real race with another worker (Error::AlreadyCompleted) and any
job execution that returned an error via process_result. The outer "job"
span recorded "job already completed by another worker" for both, so
every failed Python/bun/etc. script ended up with that misleading
otel.status_message even though the real error was correctly recorded
on the inner job_postprocessing span.
Replace the bool with a JobOutcome enum (Completed / Failed { description }
/ AlreadyCompleted). Failed carries the truncated error string, so the
outer span's Status.message now reflects the actual cause.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: extract user-facing error from error_value for span description
Cubic flagged that capturing e.to_string() before the match meant
ExitStatus failures (the most common failure mode for script jobs)
ended up with the generic "exit status: …" string rather than the
script error extracted from job logs by extract_error_value.
Move the description capture to after error_value is built and
deserialise it as ErrorMessage to pull out the structured message.
Falls back to "Job failed" if the value isn't shaped that way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: use permissive description extraction for OTLP status message
Cubic flagged that strict ErrorMessage parsing downgraded real failures
to a generic "Job failed" whenever the result wasn't shaped as
{message, name} — e.g. agent-worker's "See logs for more details" raw
string, or runtime-written result.json files with a different shape.
Switch to parsing as serde_json::Value and pulling .message out if it's
a string, falling back to the whole value if it's a bare string. Only
fall back to "Job failed" when neither is available.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* feat: support assigning a worker tag to app/raw-app inline scripts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: omit empty tag field from inline script raw_code payload
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* style: shrink tag popover width
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: pair PG arg type with actual Rust binding to keep query_typed_raw safe
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(pg): wrap encoder errors with arg context, add fallback test
Followups on #8999 review:
- Wrap rust-postgres "error serializing parameter N" failures with the arg
name, JSON value kind, and asserted Postgres type plus a hint about an
explicit cast — so users see actionable context instead of an opaque
WrongType.
- Drift-prevention meta-test: assert otyp_to_pg_type and convert_val agree
on the Type for every recognised arg_t when the JSON value matches its
natural Rust kind. Catches future drift if either side changes.
- Integration test for the prepare + query_raw fallback path: confirms
unrecognised arg_t (custom enum) is routed through prepare and the
server-resolved type appears in the failure surface — flips into a
test failure if a regression accidentally routes unrecognised types
through query_typed_raw.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): add otyp_inferred flag + regex-based placeholder renumbering
Two follow-ups from the review of #8999:
1. **Issue #1 (Number/Bool + explicit text decl in WHERE)**
Add `Arg::otyp_inferred: bool` to the parser. The PG SQL parser sets
it `true` only at the "no info → fall back to text" site (bare `$N`,
no inline cast, no `-- $N (TYPE)` decl). All other arg sources keep
it `false`.
In `convert_val` this flag distinguishes:
- explicit text-like target (`-- $1 (text)` or `$1::text`) — coerce
`Bool`/`Number` → `Box<String>` so `WHERE text_col = $1` works
(`text = text` operator). Pre-#8988 behaviour, restored.
- parser-default text (bare `$N`) — bind the value's natural Rust
type so the regression case (`Value::Bool` against a real `bool`
column via `CAST AS bool`) keeps working.
`Arg` is in `windmill-parser`; the new field has `#[serde(default)]`
so persisted signatures stay backward-compatible.
2. **Issue #4 ($5/$50 substring rewrite collision)**
Replace the per-index `String::replace` chain (which turned `$50`
into `$10` when oidx=5 was processed first) with a single regex
pass. `\d+` is greedy, so `$5` and `$50` match as distinct units;
indices outside the mapping are left intact.
3. Tests:
- parser: `test_parse_pgsql_otyp_inferred_flag` covers bare/inline-
cast/decl/mixed shapes.
- executor unit: `convert_val_bool_against_every_arg_t` and
`convert_val_*_number_*` split each text-like target into explicit
vs inferred expectations.
- executor unit: `renumber_sparse_placeholders_no_collision`.
- integration: `test_postgresql_arg_type_combinations` adds 4 cases
covering decl(text)+Number/Bool in WHERE, bare $1+Bool, and
sparse positional args ($5/$50).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg+sdk): enum support, extended String arms, position-aware $N rewrite, SDK quality
Backend:
1. **`AnyTextValue` ToSql/FromSql wrapper**: vanilla `tokio_postgres`'s
`ToSql for String` / `FromSql for String` reject `Kind::Enum` and
`Kind::Domain` even though the wire format is plain UTF-8. The wrapper
accepts those kinds in both directions. End result: explicit
`$1::my_enum` / `CAST($1 AS my_enum)` casts now round-trip without the
ugly `CAST($1::text AS my_enum)` workaround, AND `SELECT enum_col`
results come back as JSON strings instead of erroring at the FromSql
layer.
2. **#10 — Value::String → numeric/real/double/oid/bool**. Without these
arms, a string-encoded value (`"3.14"`, `"true"`) for a non-text /
non-temporal arg_t fell through to `Box<String> + TEXT`, which then
failed at the server (no implicit cast text→numeric in expression
context). Now strings are parsed into the matching native type with
clear error messages on parse failure.
3. **Position-aware `$N` rewrite**: replaces the regex-based renumbering
(which fixed the `$5/$50` substring collision but still walked through
string literals and comments, mangling `'price: $5'` etc.) with a
walk over `parse_pg_statement_arg_positions` — the same
string/comment/dollar-quote-aware tokenizer used for index discovery.
Adds `parse_pg_statement_arg_positions` to the parser's public API.
SDK:
4. **BigInt support**: `JSON.stringify(BigInt)` throws. The SDK now
stringifies bigints before serialisation; the executor accepts
numeric strings into BIGINT arg slots via the existing
`Value::String → INT8` parsing arm. SDK-side `inferSqlType` is split
so `BigInt` always resolves to `BIGINT` (was reaching
`Number.isInteger(BigInt)` which returns false → wrong default).
5. **Homogeneous array auto-tag**: `${[1,2,3]}` against an `int[]` column
now emits `$1::BIGINT[]` instead of `$1::JSON`. Detection covers
primitive types only (number / bigint / string / boolean); mixed or
nested arrays still fall back to JSON. Mixed int/float widens to
`DOUBLE PRECISION[]`.
6. **`.query()` positional bug**: previously the `.query()` method
abused the template-tag builder, which appended `$N::TYPE` after the
user's literal SQL string instead of binding by position
(`SELECT $1, $2` became `SELECT $1, $2$1::BIGINT`). Now `.query()`
builds the executor-shaped content directly: a `-- $N argN (TYPE)`
declaration block followed by the user's SQL verbatim.
Tests:
- Parser: `test_parse_pg_statement_arg_positions_skips_strings_and_comments`
asserts string literals, comments, and dollar-quoted blocks don't
produce positions (so renumbering doesn't mangle them).
- Executor unit: `renumber_sparse_placeholders_no_collision_no_string_mangling`
uses the new position-aware path and includes string-literal + comment
+ `$$…$$` cases. Existing convert_val tests grow to cover new
String→numeric/real/double/oid/bool arms.
- Integration: `test_postgresql_arg_type_combinations` adds 13 cases
(enum round-trip both directions, string→numeric/real/double/bool/oid,
string-literal `$N` non-mangling). The prepare-fallback test now
asserts SUCCESS (not failure) for enum encoding via AnyTextValue.
- SDK: new `typescript-client/tests/sqlUtils.test.ts` (42 tests)
exhaustively covering inferSqlType primitives + arrays,
parseTypeAnnotation, datatable() template tag (with all the new
shapes — BigInt, homogeneous arrays, RawSql, schema preamble),
datatable().query() positional, and ducklake() shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): replace DISCARD ALL with curated reset (preserves typeinfo cache)
Found while exhaustively probing custom-type DX: every cached-connection
reuse was running `DISCARD ALL`, whose included `DEALLOCATE ALL`
deallocates *all* prepared statements server-side — including the typeinfo
statements that tokio_postgres caches per-Client to resolve custom enum /
domain Oids. tokio_postgres still held `Statement` objects whose names
the server had forgotten, so the next custom-type query failed with
intermittent "prepared statement \"sN\" does not exist" errors. The
failure was easy to reproduce: any sequence that forced typeinfo lookup
for two different custom-type kinds on the same cached connection (e.g.
enum followed by domain) would hit it.
Replace `DISCARD ALL` with a curated reset that explicitly targets the
state we actually care about, *without* touching prepared statements:
RESET ALL — GUC parameters (search_path, application
_name, statement_timeout, …)
RESET SESSION AUTHORIZATION — undoes both `SET SESSION AUTHORIZATION`
and `SET ROLE` (RESET ALL does NOT —
these aren't GUC parameters, so without
this an elevated role from a previous
job would silently leak)
UNLISTEN * — drops LISTEN registrations
CLOSE ALL — closes open cursors
Trade-off: temp tables, advisory locks (session-scoped), and user-created
PREPARE statements may persist across cached-connection reuse — rare in
datatable / PG-script workloads. tokio_postgres's typeinfo cache survives
intact, so custom enum / domain queries are fast on subsequent reuse.
Tests:
- `test_postgresql_custom_types_on_cached_connection` — runs 10×
alternating enum + domain queries on a cached connection. Pre-fix this
failed with `prepared statement "sN" does not exist` after the first
reuse; post-fix passes.
- `test_postgresql_set_role_does_not_leak_across_cached_connection` —
switches `SET ROLE` and `SET SESSION AUTHORIZATION` to a non-postgres
role, then runs a follow-up job and asserts current_user/session_user
are restored. Specifically catches the case where someone might switch
back to `RESET ALL` alone (which doesn't cover SET ROLE / SESSION
AUTHORIZATION) and silently introduce a permission-leak vector.
- All existing session-isolation tests
(`test_postgresql_cached_connection_resets_session`,
`test_postgresql_single_worker_session_isolation`,
`test_postgresql_100_jobs_cached`) continue to pass.
Found via end-to-end probing of datatable / PG-script DX, not previously
covered: the existing isolation tests only did `SET ROLE postgres`, the
connecting user, so the leak was invisible.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): address PR #8999 review (cubic + claude)
cubic (P1, real bug):
- `convert_vec_val` for `timetz` array asserted `Type::TIMETZ_ARRAY`, but
chrono `NaiveTime` only encodes for TIME (same caveat as the scalar
arm). Switch to `Type::TIME_ARRAY`; rely on PG's implicit `time→timetz`
assignment cast at the column site. Add an explicit unit test.
claude (#1, silent failure → explicit error):
- `Bool` + explicit `(char)` / `(character)` decl previously silently
bound BOOL, hoping the server would cast at the use site — but PG has
no implicit `bool→char` and the resulting error
("operator does not exist: bool = char") was opaque. Now error at
bind time with an actionable hint to use `bool` decl or pass the
value as a "t"/"f" string.
claude (#2, asymmetry doc):
- Object/Array still coerce to text on `matches!(typ, Typ::Str(_))`
(covers both explicit AND inferred-default text), unlike Bool/Number
which key on `explicit_text_target`. The asymmetry is intentional
(no implicit `jsonb → text` cast in expression context vs PG having
implicit `bool/int → text` casts) — added a body comment so future
maintainers don't try to "align" them.
claude (#3, perf):
- `parse_pg_statement_arg_indices` and `parse_pg_statement_arg_positions`
walked the SQL tokenizer twice. Fold into a single pass that derives
the index set from the position list.
claude (#4, fmt drift):
- `cargo fmt` over the parser crates I touched with perl scripts in the
earlier commit (windmill-parser-{sql,bash,ts,go,php,java,csharp,nu,py,
rust,graphql,yaml,r}). Net cosmetic.
claude (#5, parseTypeAnnotation):
- One-line caveat in the SDK's `parseTypeAnnotation` that the returned
string is presence-only (e.g. `${x}::DOUBLE PRECISION` returns
`"DOUBLE"`, `CAST(${x} AS int)` returns `"int)"` — neither matches a
real PG type, but the only consumer just checks `!== undefined`).
While here — discovered + fixed independently while exhaustively probing
DX:
- **Replace `DISCARD ALL` with curated reset** (`RESET ALL; RESET
SESSION AUTHORIZATION; UNLISTEN *; CLOSE ALL;`). DISCARD's
`DEALLOCATE ALL` killed tokio_postgres' typeinfo cache, producing
intermittent `prepared statement "sN" does not exist` errors on
custom-type queries after cached-conn reuse. New regression tests:
`test_postgresql_custom_types_on_cached_connection` and
`test_postgresql_set_role_does_not_leak_across_cached_connection`
(the latter catches the case where someone might switch back to
`RESET ALL` alone and silently introduce a permission-leak vector —
RESET ALL doesn't cover SET ROLE / SET SESSION AUTHORIZATION).
- **ISO-8601 timestamp results** (`pg_cell_to_json_value`). Pre-fix
`TIMESTAMP` was rendered with a space separator ("2024-01-15 10:30:00")
and `TIMESTAMPTZ` with " UTC" suffix ("2024-01-15 10:30:00 UTC") —
neither parseable by `date-fns parseISO`, JavaScript `new Date()` is
lenient enough to handle them but several frontend `App*Input.svelte`
components use parseISO and fail silently. Switched to ISO-8601 with
`T` separator and `+00:00` offset; arg-parsing path still accepts the
legacy " UTC" suffix for back-compat.
Test coverage:
- 17/17 unit (`pg_executor::tests`)
- 9/9 integration (`backend/tests/worker.rs`, `test_postgresql_*`)
- 27/27 parser (`windmill-parser-sql`)
- 42/42 SDK (`typescript-client/tests/sqlUtils.test.ts`)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): bounded one-shot warning on numeric precision loss + ISO-8601 + NaN handling
Found while probing PG-script DX with millions of numeric cells:
1. **Numeric precision-loss warning**: `numeric` results are still serialised
as JSON Number (back-compat — switching to JSON String would silently
break user code doing arithmetic on results), but we now detect
`Decimal -> f64 -> Decimal` round-trip failure and emit a single
job-log warning recommending a `::text` cast in the SQL. Bounded by
`NUMERIC_PRECISION_CHECK_BUDGET = 256` cells per query (one atomic
load + one fetch_sub on the hot path; first lossy value
short-circuits to a single load thereafter). Worst-case overhead on
a 1M-cell numeric-heavy query: ~25µs of checks + 5ns × N atomic
loads (vs. ~100ms unbounded).
2. **ISO-8601 timestamps**: `pg_cell_to_json_value` previously returned
`"2024-01-15 10:30:00"` (TIMESTAMP) and `"2024-01-15 10:30:00 UTC"`
(TIMESTAMPTZ) — neither parseable by date-fns `parseISO`, which is
what the apps `App*Input.svelte` components use, so timestamp values
silently failed to round-trip into date pickers. Switch to ISO-8601
(`T` separator + `+00:00` offset) on the result side; arg-parser
continues to accept the legacy `" UTC"`-suffixed format for
back-compat.
3. **Float NaN / Infinity results**: `Number::from_f64` returns None for
NaN / ±Inf, which `pg_cell_to_json_value` was raising as
"invalid json-float" — failing the *entire* query if any cell held
one of these special values. Now serialise them as JSON strings
("NaN", "Infinity", "-Infinity") and let the rest of the row come
through. Arg-side: `s.parse::<f64>()` already accepts the same
strings.
Tests:
- `decimal_fits_f64_losslessly_predicate` — covers fits / doesn't-fit
cases for the precision-loss predicate.
- `precision_check_budget_caps_per_query_overhead` — locks in the
budget cap and the loss-flag short-circuit.
- All 9 PG integration tests + 17 unit tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): add pg_advisory_unlock_all to reset; warn on missing args; honor decl defaults
While probing PG-script DX further found three more frictions:
1. **Advisory lock leak** (cubic P2): switching from `DISCARD ALL` to
`RESET ALL; RESET SESSION AUTHORIZATION; UNLISTEN *; CLOSE ALL;`
meant session-scoped advisory locks (`pg_advisory_lock`) leaked
across cached-connection reuse. Add `SELECT pg_advisory_unlock_all()`
to the chain — `DISCARD ALL` covered this implicitly via
`DISCARD PLANS / DEALLOCATE / pg_advisory_unlock_all` and we lost it
in the switch.
2. **Missing-arg silent NULL**: an arg declared in the SQL (e.g.
`-- $1 amount (numeric)`) but not provided in the args object was
bound as NULL with no error / warning. Misspelling the key in the
args object silently produced a row of NULLs — a notorious DX
debugging trap. Now: collect the names of declared-but-missing
args during dispatch and emit a single one-shot warning to the job
logs at end-of-query naming each one. Bound NULL is preserved for
back-compat.
3. **Declaration defaults ignored**: `-- $1 a (int) = 5` carries
`arg.default = Some(Number(5))`, but the dispatch fell straight to
NULL when the arg was missing. Now: respect the default —
user-supplied value > declaration default > NULL. Also fixes the
warning logic above (only warn for args that *don't* have a default).
Tests: existing 19 unit + 9 integration pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(pg): multi-word PG types with [] suffix lost the array-ness; array arms accept stringified values
Two more frictions found while probing SDK end-to-end against a real
datatable resource:
1. **Multi-word array types lose the [] suffix in the parser**.
`transform_types_with_spaces` recognises aliases for "double
precision", "character varying", "timestamp with time zone", etc.
but its return type was `&'a str` — only the bare alias, never with
a trailing `[]`. The `RE_CODE_PGSQL` regex's `\w+` captures stop at
the first space, so the regex's own `(?:\[\])?` array-suffix branch
sees only `"double"` (not `"double precision[]"`); the `[]` was
silently lost. Result: `$1::double precision[]` (which the SDK now
emits for homogeneous float arrays via the new auto-tag) routed
through `Value::Array → Type::JSONB` and the server failed with
"cannot cast type jsonb to double precision[]".
Fix: switch `transform_types_with_spaces` to return `Cow<'a, str>`
and re-check the trailing bytes after a multi-word match. If they
start with `[]`, return `format!("{alias}[]")` — Owned. Single-word
types and the no-match path keep returning Borrowed slices, so no
allocation in the hot path.
2. **Array arms in `convert_vec_val` rejected stringified values for
numeric / int* / bool / oid / real / double**. The scalar `convert_val`
already parses strings into the matching native type for these arg_ts,
but the array variant only accepted JSON-native counterparts. Sending
`["1.5", "2.5", "3.5"]` against `$1::numeric[]` (e.g. via `unnest` for
bulk loading, or `JSON.stringify(BigInt[])` round-trip) failed with
"Mixed types in array". Now the array arms mirror the scalar ones —
`as_<native>().or_else(|| as_str().and_then(parse))` — so both shapes
round-trip cleanly.
Tests: 19 unit + 9 integration pass; existing parser tests cover the
multi-word array forms (the regex-cap behaviour didn't break for
single-word types, and Cow plumbing is transparent to all callers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(parsers): add otyp_inferred field to Arg literals in tests + 3 missed src files
CI failures: the perl-driven sweep that added `otyp_inferred: false` to
every `Arg { ... }` literal when I introduced the field in the parser
schema covered `src/lib.rs` files but missed:
- parsers/windmill-parser-bash/src/lib.rs (mass-edited but a
later format pass un-applied a few sites)
- parsers/windmill-parser-go/src/lib.rs (same)
- parsers/windmill-parser-graphql/src/lib.rs (same)
- parsers/windmill-parser-nu/tests/tests.rs (test file — not
swept the first time)
- parsers/windmill-parser-ts/tests/tests.rs (test file — same)
Also tightened the regex to handle `oidx: None` without the trailing
comma (some test files had the field as the last initialiser line).
`cargo build --features <CI feature combo> --workspace --all-targets`
is clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sdk): Date → TIMESTAMPTZ; NaN / ±Infinity → string
Two more frictions found while running the actual SDK end-to-end against
a live datatable resource:
1. **JS `Date`** fell into the typeof "object" branch and was tagged
`::JSON`. It worked accidentally for `${date}::timestamptz` via PG's
`json → text → timestamptz` implicit cast chain, but `${date}` against
a `timestamptz` column without a user-supplied cast bound the value
as a JSON string and the comparison `timestamptz = json` failed. Now:
`inferSqlType` recognises `Date` and tags `::TIMESTAMPTZ`;
`serializeArgValue` emits `Date.toISOString()` so the executor's
`Value::String → TIMESTAMPTZ` arm parses it cleanly.
2. **JS `NaN` / `±Infinity`** silently became NULL. `JSON.stringify(NaN)`
returns `"null"` per the JS spec, so the value reached the executor as
JSON null — the SDK's `::DOUBLE PRECISION` tag then bound a NULL
double. Fix: detect non-finite numbers in `serializeArgValue` and
stringify them as `"NaN" / "Infinity" / "-Infinity"`. The executor's
`Value::String → FLOAT8` arm (`f64::from_str`) accepts these literals
directly, and the result-side already renders the values as JSON
strings (matching round-trip).
SDK unit tests grow from 42 → 44 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(pg): integration coverage for multi-word arrays + stringified array elements
Locks in the two array fixes from the previous commit
(`fix(pg): multi-word PG types with [] suffix lost the array-ness`)
with end-to-end cases in `test_postgresql_arg_type_combinations`:
- `double precision[]`, `character varying[]`, `timestamp without time
zone[]` — verifies the parser keeps the `[]` suffix after multi-word
alias resolution.
- `numeric[]` / `int[]` / `bool[]` from stringified primitives — verifies
the array arms of `convert_vec_val` apply the same string-coercion
the scalar arms do.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style: fix indentation drift on otyp_inferred lines
cargo fmt cleanup of leftover indentation where the perl-driven sweep
that introduced the otyp_inferred field landed at the wrong column.
No behaviour change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
tracing-opentelemetry only recognizes otel.status_code and
otel.status_message as fields that map to the OTLP Status proto.
The previously-used otel.status_description fell through to the
generic attribute recorder, leaving Status.message unset and
preventing OTLP consumers from filtering spans on error status.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: ansible delegate_to_git_repo install_requirements, dynamic fields, --limit
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: include .yaml variants in collections/roles requirements lookup
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Always send queries as unnamed prepared statements (query_typed_raw)
when arg types resolve via otyp_to_pg_type. This eliminates the
intermittent "prepared statement \"sN\" does not exist" error reported
on datatable scripts whose Postgres connection sits behind a
transaction-mode pooler (PgBouncer/Supabase pooler/RDS Proxy), where
prepare and execute can land on different backend connections.
The previous code only used the unnamed-statement path when the parser
detected at least one explicitly typed arg; datatable-generated SQL
with bare $1/$2 (relying on inline ::cast hints) fell back to
prepare + query_raw and accumulated named statements (s0, s1, ...,
s882, ...) on the cached connection, which the pooler then dropped.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: set OTEL span status on failed jobs and add stderr severity toggle
Record otel.status_code=ERROR and otel.status_description on the job /
job_postprocessing tracing spans when a job fails, so OTel exporters
(Sentry, Honeycomb, Datadog) see the standard span-level failure signal
instead of just the success=false attribute. Description is truncated to
512 chars to keep span payloads bounded.
Add OtelSettings.stderr_default_severity instance setting (error | warn |
info | debug, default error) to let operators downgrade the OTEL severity
used for job stderr output. Python logging routes every record >= WARNING
to stderr, so the historical blind stderr->error mapping produces false
positives for scripts like dlt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref for stderr severity toggle
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: clarify OTEL span description for already-completed jobs
When handle_queued_job returns Ok(false) on Error::AlreadyCompleted
(another worker already finished the job during a race), the span
was labeled "job returned false" which is opaque to operators.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: parse Python logging severity from job stderr
Replace the global stderr_default_severity instance toggle with a
worker-side classifier that recognizes Python's canonical
basicConfig() format (LEVELNAME:logger.name:message) and emits the
corresponding tracing level. Lines that don't match keep the
historical tracing::error! fallback, so genuine failures still
surface and non-Python output is unaffected.
Removes StderrLogSeverity, STDERR_LOG_SEVERITY, and the
otel.stderr_default_severity field; adds
classify_python_logging_line in windmill-common.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: cover classify_python_logging_line + fix truncate_description doc
Add unit tests for the Python stderr-severity classifier and correct
the truncate_description docstring to say "bytes" (the cap is byte-
based, with UTF-8 boundary rounding for safety). Addresses Claude
review feedback on PR #8918.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: hugocasa <hugo@casademont.ch>
* feat: support S3Object input args in native SQL scripts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: review fixes from local-review
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* update parser
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: support restart from steps inside BranchOne, ForLoop, Subflow
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: preserve original job kind in nested restart, support expanded subflow steps
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: read selected iteration from graph state for nested ForLoop restart
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: iteration selectors per ForLoop in restart popup, more nested restart tests
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: extract useNestedRestartState composable
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: cover deployed-subflow + FlowDependencies path in nested restart
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update sqlx prepare cache
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: detect BranchOne/ForLoop ancestors inside expanded subflows for nested restart
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: hide restart button for non-restartable steps (parallel containers, untaken branches)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address review feedback on nested restart PR
- preview FlowRestartButton: hide nested case (chain UUIDs aren't resolvable in
preview path; users can use the run page for nested restart instead)
- branchOneAncestorMatchesOriginal: be permissive when status isn't reachable
(don't hide the button for BranchOnes nested deeper than top-level)
- worker_flow.rs: apply nested_restart_payload swap on the is_simple ForLoop
fast path too, so simple iterations don't bypass restart spawn interception
- FlowStatusViewer: reset expandedSubflows cache on jobId change; drop
$bindable({}) banned pattern for the new prop
- API resolver: validate the leaf step exists before returning (fail-fast)
- doc fix: branch_or_iteration_n is 0-based, not 1-based
- selectedJobStepIsTopLevel reset on early-return in composable
- comment iterationCounts collision caveat
- new HTTP-level integration tests covering the API endpoint contract:
happy path (top-level + nested), unknown step, out-of-range iteration,
parallel-loop rejection
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* revert: remove unreachable nested-restart swap on is_simple ForLoop fast path
The swap is unreachable in valid flows: `is_simple_modules` requires the body
to be a single `script` / `rawscript` / `flowscript` (per `FlowModule::is_simple`),
none of which spawn flow-kind children. Any nested-restart chain targeting a
leaf inside such an iteration is rejected by the API at leaf validation. Even
if a chain reached the worker via `JobPayload::RawFlow.restarted_from`, the
resulting `RestartedFlow` would fail to push (script kind isn't a flow kind).
Replaced the swap with an explanatory comment so the next reader knows why
the symmetry with the non-simple path was deliberately not added.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: handle undefined expandedSubflows + tighten branchOne match check
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: split flow-dep job tx so subprocesses don't hold row locks
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: link flow version from run page to pinned flow viewer
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: time-out dep job phase 1/3 db ops and surface error on flow page
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: dissolve dep_map in phase 1 and recheck flow version unconditionally
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address PR review — view-latest reload, decimal truncation, app version
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: keep dissolve in phase 3 for relative-import dep jobs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: trim verbose comments and refresh sqlx offline cache
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address cubic — propagate dissolve errors, include workspace in reload key
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(wac): recognize @workflow main, list WAC in scripts/list, run preprocessor
Three workflow-as-code bug fixes:
- #8945: Python WAC template with `@workflow async def main(...)` was not
detected as `auto_kind = "wac"`. The detection only ran when no `main`
function was found. Hoist the heuristic so it runs whether or not `main`
is the entrypoint.
- #8946: `scripts/list?kinds=script` filtered out WAC scripts because they
set `auto_kind = 'wac'` and the SQL hid everything that wasn't NULL.
Allow both NULL and 'wac' (still excluding 'lib' library scripts).
- #8947: Preprocessor functions defined alongside a WAC workflow were
ignored. Inject the preprocessor invocation into the Python WAC wrapper
so it runs before the workflow on the first iteration, then plumb the
preprocessed args through `handle_wac_v2_output` so inline child
re-runs see the post-preprocessor args via `checkpoint.input_args`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(wac): integration tests for #8946 (scripts/list) and #8947 (preprocessor)
- test_scripts_list_includes_wac: hit GET /scripts/list?kinds=script and
assert WAC scripts are in the response (would have failed pre-#8946 fix
because of the auto_kind IS NULL filter).
- test_python_wac_v2_with_preprocessor: deploy a Python WAC script with a
preprocessor, run with raw event args, assert the workflow saw the
preprocessed shape and v2_job.args/preprocessed were updated.
- New wac_preprocessor.sql fixture with auto_kind = 'wac' set explicitly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(wac): address PR review feedback
Five review fixes:
- python_executor.rs: WAC preprocessor now runs inside the wrapper's
`try:` block so failures route through the same `result.json` error
serializer as workflow failures. Switched async-coroutine handling
from deprecated `asyncio.get_event_loop().run_until_complete(...)` to
`asyncio.run(...)` (the recommended primitive on 3.10+).
- bun_executor.rs: when copying preprocessed args into
`checkpoint.input_args`, surface JSON parse failures via `?` instead
of silently coercing to `Value::Null` (which would persist a corrupted
arg into every child re-run). Also collapsed the redundant double
iteration into a single pass.
- windmill-api-scripts/scripts.rs: switched the runnable-script filter
from an allow-list (`auto_kind IS NULL OR = 'wac'`) to a deny-list
(`<> 'lib'`), so future `auto_kind` values aren't silently filtered
from triggers/dropdowns.
- windmill-parser-py: aligned the parser's WAC heuristic with the
runtime detector `is_wac_v2_py` — `@task` is now optional, matching
the runtime which says workflows that only use inline `step()` are
still WAC. Added a regression test `test_parse_python_wac_step_only`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(nativets): forward OTEL-prefixed console logs to tracing events
Nativets jobs run in-process and bypass the handle_child.rs stdout loop
where `OTEL: ` lines are turned into `tracing::event!` calls when
`OTEL_JOB_LOGS=true`. Apply the same prefix handling in the nativets
log receiver so `console.log("OTEL: ...")` reaches the OTEL exporter
like it does for other runtimes.
Moves `OTEL_JOB_LOGS` and `OTEL_PREFIX` into windmill-common so both
crates share the same definition.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(nativets): emit job_log tracing target so logs reach OTEL bridge
For non-native runtimes, `lines_to_stream` → `process_streaming_log_lines`
(EE) emits every stdout line as `tracing::info!(target: "windmill:job_log", ...)`,
which is picked up by the EE `LogContextBridge` and exported to OTEL
(the bridge's filter is `EnvFilter` only, not the targets filter that
drops `windmill:job_log` from stdout/file sinks).
Nativets delivers logs in-process via a channel, so it never goes
through that path and console.log output only reached the Windmill UI.
Emit the same `windmill:job_log` event per line from the nativets log
receiver.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: 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>
* 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>
* fix: support windmill chat answer override
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: remove output fallback from chat override
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: handle non-string chat answer overrides
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: persist powershell workspace deps in deployed script lock
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: trigger dep job for powershell scripts on deploy
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
A BranchOne predicate expression that threw was propagated as a
flow-level error, bypassing the flow's failure_module — especially
silent when nested inside a forloop with skip_failures: true, where
the failed iteration was swallowed with no handler ever invoked.
Catch the predicate-eval error inside compute_next_flow_transform's
BranchOne case, return a new NextFlowTransform::StepFailure variant,
and have push_next_flow_job route it through
update_flow_status_after_job_completion with success=false. Predicate
errors now behave exactly like a failing script step: failure_module
runs when defined, skip_failures still skips, workspace error handler
fires when the flow fails.
Closes#8889
* refactor: create windmill-ai crate and move base AI types from windmill-common
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move worker AI types to windmill-ai crate
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move QueryBuilder trait and StreamEventSink abstraction to windmill-ai
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add base64 dependency to windmill-ai for bedrock PDF support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add windmill-ai refactor plan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: address PR review — remove dead bedrock feature, add boxed_sink helper, move plan to docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: track dollar-quoted strings in SQL block splitter
Queries like `CREATE FUNCTION ... AS $$ ... ; ... $$ LANGUAGE plpgsql;`
were being shredded on every `;` inside the function body because the
SQL splitter's state machine didn't recognize PostgreSQL dollar-quoted
strings. Add an `InDollarQuote(tag)` state so `$$ ... $$` and
`$tag$ ... $tag$` regions are treated as a single quoted span.
Opt-in via a new `track_dollar_quotes` flag on `parse_sql_blocks`;
enabled for PostgreSQL and DuckDB, disabled for MySQL/Oracle/BigQuery/
Snowflake which don't support the syntax.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: make windmill-parser-wasm a self-contained workspace
The wasm parser crate is excluded from the backend workspace (its
nightly-only `cargo-features = ["panic-immediate-abort"]` would break
stable cargo on the whole workspace), but its manifest still used
`.workspace = true` inheritance — which fails with "failed to find a
workspace root" once the parent no longer considers it a member.
Declare the crate as its own workspace by adding `[workspace]`,
`[workspace.package]`, and `[workspace.dependencies]` tables. Mirror
the relevant entries from the parent `backend/Cargo.toml` (same
version specs, same path targets) so resolution stays byte-identical
to what the parent would have produced.
Also:
- Teach `.github/change-versions.sh` (+ mac variant) to update this
crate's own `Cargo.toml` version and bulk-bump the `windmill-*`
entries in its `Cargo.lock` on each release.
- Bump the frontend's pinned `windmill-parser-wasm-regex` to 1.688.0
to match the freshly-built package, and refresh `package-lock.json`.
- Regenerate the wasm crate's `Cargo.lock` from scratch (first build
under the new workspace re-resolves the full graph; target-gated
deps from sibling crates like `windmill-parser-py-imports` are
now recorded in the lockfile but not compiled when targeting
wasm32).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract the MSSQL s3 ingest+upload logging pattern into a reusable
`s3_stream_and_upload_with_logs` helper and apply it to the PostgreSQL,
MySQL, OracleDB, BigQuery, and Snowflake executors. Each s3 streamed
query now emits periodic progress lines, an ingest-done line, and an
upload+transcode-done line to the job output, matching MSSQL.
`convert_json_line_stream` now returns `BoxStream<'static, _>` so the
output stream can be forwarded to `s3.upload` from inside the generic
helper without lifetime gymnastics; the two existing callers already
boxed the result, so behavior is unchanged.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* perf: speed up mssql s3 ingest and add phase logs to job output
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: drop mssql s3 progress interval to 10s for better visibility
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: drop transient tests that compared against removed code path
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: log boolean predicate eval errors to root flow logs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: use if-let over match for predicate error logging
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: avoid persisting system prompts in ai memory
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: keep ai memory cleanup write-side only
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add bedrock prompt caching for claude
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: add bedrock memory regression
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: gate bedrock prompt caching by model id
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: link bedrock caching allowlist source
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>