Commit Graph

1785 Commits

Author SHA1 Message Date
Ruben Fiszel f8467f38c8 fix: prevent cross-tenant DNS poisoning via writable /etc in nsjail (#9194)
* 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>
2026-05-17 12:54:38 +00:00
Ruben Fiszel 81b5736106 fix: atomic bundle cache writes to prevent parallel cold-load race (#9186)
* 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>
2026-05-15 21:04:16 +00:00
windmill-internal-app[bot] 69b3141e03 fix: apply pip_local_dependencies filtering to deployed scripts with populated lockfiles (#9178)
* fix: apply pip_local_dependencies filtering to deployed scripts with populated lockfiles

* refactor: share pip_local_dependencies filtering helper, log ignored deps

* test: split pure filter core out for unit testing, cover #-preservation

---------

Co-authored-by: windmill-internal-app[bot] <1429786+windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-05-15 12:46:37 +00:00
windmill-internal-app[bot] e1819313e1 fix: aggregate wait time should target the true root job, not flow_innermost_root_job (#9177)
* 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>
2026-05-15 12:24:29 +00:00
Ruben Fiszel d48d61cc79 feat(otel-tracing-proxy): configurable tracing MITM NO_PROXY hosts (#9169)
* feat(otel-tracing-proxy): configurable NO_PROXY hosts

* refactor(otel-tracing-proxy): NO_PROXY only governs job-side bypass

* fix(otel-tracing-proxy): restore empty NO_PROXY default

* test(otel-tracing-proxy): unit tests for NO_PROXY normalization

* fix(otel-tracing-proxy): gate normalize_no_proxy_hosts to EE features
2026-05-14 14:03:47 +00:00
Ruben Fiszel 33bf01b627 fix(python): preserve strings containing Infinity/NaN in result JSON (#9149)
* 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
2026-05-13 15:49:36 +00:00
Ruben Fiszel 4d0f2c26a1 fix(bun): pass --preserve-symlinks on unbundled execution (#9147)
* 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>
2026-05-13 13:04:31 +00:00
windmill-internal-app[bot] 818cb31fbc fix: send flow push-loop ping outside transaction so zombie monitor sees it (#9136)
* fix: send flow push-loop ping outside transaction so zombie monitor sees it

* fix: keep flow push-loop ping using now() with reusable sqlx cache

---------

Co-authored-by: windmill-internal-app[bot] <1429786+windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-05-13 12:32:35 +00:00
centdix 17cf538a2d refactor: move ai providers to windmill-ai (#9120) 2026-05-12 14:09:31 +00:00
centdix 6f7d31e56b refactor: move ai image handling to windmill-ai (#9098) 2026-05-11 14:59:40 +00:00
centdix 27acbbf3d5 refactor: move ai sse plumbing to windmill-ai (#9059)
* 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>
2026-05-11 10:01:45 +00:00
Ruben Fiszel 98ff146cfa fix(python): verify wheel RECORD on cache pull/install, finalize piptar (#9090)
The Python per-package dependency cache could persist an incomplete wheel
extraction with `.valid.windmill` set, then propagate that broken artifact
to every worker through the object store. Customer hit this on
argon2-cffi==25.1.0 (missing argon2/_utils.py), and previously on
botocore/httpx (truncated tars). Symptom is a runtime ImportError that
looks like a missing dependency declaration rather than a Windmill bug.

Three changes that together stop the propagation:

1. After `pull_from_tar`, parse the wheel's `<dist-info>/RECORD` and
   confirm every listed path exists on disk before writing
   `.valid.windmill`. On failure, wipe the directory and fall through
   to a fresh local install — the next install also self-heals the
   broken object-store entry by pushing a fresh tar.

2. After `uv pip install` succeeds, run the same RECORD check before
   queuing the piptar upload or writing `.valid.windmill`. A bad install
   never becomes the source of a broken tar in the object store.

3. Finalize the tar (`drop(tar.into_inner()?)`) before reading its bytes
   for upload, so we never push an unfinalized archive (no end-of-archive
   marker) to the object store.

Verified with a 60-package end-to-end integration test (first-fill →
clear-local-cache → re-pull-from-objectstore → corrupt-objectstore-tar
→ detect-and-self-heal). All 27 packages on the live test pulled cleanly,
and the deliberately corrupted argon2-cffi tar was caught with the exact
expected log line ("wheel RECORD lists files missing on disk: argon2/_utils.py")
and replaced with a fresh tar.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:56:51 +00:00
hugocasa dd5320205f feat: parse windmill_failure field to tag run as failure (#9073)
* feat: parse windmill_failure field in job result to tag run as failure

* feat: preserve top-level fields when windmill_failure tags a run as failure

* fix: address review findings on windmill_manual_failure

* refactor: rename windmill_manual_failure to wm_failure and add wm_* aliases

* fix: prefer injected ManualFailure error over sibling name/message in OTel
2026-05-08 15:57:34 +00:00
hugocasa 23af6c2ea3 perf(flows): gate flow_env resolve on expr text and share cache with handle_flow (#9085) 2026-05-08 15:57:17 +00:00
Ruben Fiszel d37277d234 fix: reject root-rooted paths in ansible playbook validator on windows (#9081) 2026-05-08 09:12:33 +02:00
Ruben Fiszel e1a7c75e19 perf(flows): cache resolved flow_env per flow execution (#9079)
* perf(flows): cache resolved flow_env per flow execution

* perf(flows): tighten flow_env cache cap to 1024 and clarify memory note

* perf(flows): don't cache transient flow_env resolution failures
2026-05-08 07:43:46 +02:00
Ruben Fiszel 2067e0719f perf(flows): skip flow_env DB+transform work when no resolution is needed (#9078) 2026-05-08 04:43:07 +00:00
Ruben Fiszel 5bca03eacd fix: bubble handle_flow chaining errors to parent flow (#9058)
* fix: bubble handle_flow chaining errors to parent flow

* test: while-loop propagates inner forloop iterator failure
2026-05-06 23:47:13 +00:00
Ruben Fiszel c6f1c5e623 fix(bun): make hub script cache resilient to malformed lockfiles (#9063) 2026-05-06 23:44:11 +00:00
Ruben Fiszel eebaab9c87 fix(bun): propagate non-zero exit from generate_bun_bundle on no-DB path (#9051) 2026-05-06 10:00:46 +00:00
hugocasa 6e5a21a9c7 fix(flows): inherit flow_env in sub-flow predicates (#9042)
* 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>
2026-05-05 20:11:38 +00:00
Ruben Fiszel 192866d519 fix(flows): don't bubble error when continue_on_error is on the last step (#9029)
* 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>
2026-05-04 16:54:53 +00:00
Ruben Fiszel 1be62ea926 fix: stop sequential whileloop on iteration failure (#9028)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 16:51:59 +00:00
hugocasa 70a5880d36 fix: distinguish AlreadyCompleted from execution failure on OTLP job span (#9004)
* 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>
2026-05-01 18:25:18 +00:00
Ruben Fiszel 0c22f52b46 feat: support assigning a worker tag to app inline scripts (#9002)
* feat: support assigning a worker tag to app/raw-app inline scripts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: omit empty tag field from inline script raw_code payload

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* style: shrink tag popover width

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-01 17:21:17 +00:00
Ruben Fiszel aedf369174 fix: pair PG arg type with actual Rust binding to keep query_typed_raw safe (#8999)
* fix: pair PG arg type with actual Rust binding to keep query_typed_raw safe

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(pg): wrap encoder errors with arg context, add fallback test

Followups on #8999 review:

- Wrap rust-postgres "error serializing parameter N" failures with the arg
  name, JSON value kind, and asserted Postgres type plus a hint about an
  explicit cast — so users see actionable context instead of an opaque
  WrongType.
- Drift-prevention meta-test: assert otyp_to_pg_type and convert_val agree
  on the Type for every recognised arg_t when the JSON value matches its
  natural Rust kind. Catches future drift if either side changes.
- Integration test for the prepare + query_raw fallback path: confirms
  unrecognised arg_t (custom enum) is routed through prepare and the
  server-resolved type appears in the failure surface — flips into a
  test failure if a regression accidentally routes unrecognised types
  through query_typed_raw.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pg): add otyp_inferred flag + regex-based placeholder renumbering

Two follow-ups from the review of #8999:

1. **Issue #1 (Number/Bool + explicit text decl in WHERE)**

   Add `Arg::otyp_inferred: bool` to the parser. The PG SQL parser sets
   it `true` only at the "no info → fall back to text" site (bare `$N`,
   no inline cast, no `-- $N (TYPE)` decl). All other arg sources keep
   it `false`.

   In `convert_val` this flag distinguishes:
   - explicit text-like target (`-- $1 (text)` or `$1::text`) — coerce
     `Bool`/`Number` → `Box<String>` so `WHERE text_col = $1` works
     (`text = text` operator). Pre-#8988 behaviour, restored.
   - parser-default text (bare `$N`) — bind the value's natural Rust
     type so the regression case (`Value::Bool` against a real `bool`
     column via `CAST AS bool`) keeps working.

   `Arg` is in `windmill-parser`; the new field has `#[serde(default)]`
   so persisted signatures stay backward-compatible.

2. **Issue #4 ($5/$50 substring rewrite collision)**

   Replace the per-index `String::replace` chain (which turned `$50`
   into `$10` when oidx=5 was processed first) with a single regex
   pass. `\d+` is greedy, so `$5` and `$50` match as distinct units;
   indices outside the mapping are left intact.

3. Tests:
   - parser: `test_parse_pgsql_otyp_inferred_flag` covers bare/inline-
     cast/decl/mixed shapes.
   - executor unit: `convert_val_bool_against_every_arg_t` and
     `convert_val_*_number_*` split each text-like target into explicit
     vs inferred expectations.
   - executor unit: `renumber_sparse_placeholders_no_collision`.
   - integration: `test_postgresql_arg_type_combinations` adds 4 cases
     covering decl(text)+Number/Bool in WHERE, bare $1+Bool, and
     sparse positional args ($5/$50).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pg+sdk): enum support, extended String arms, position-aware $N rewrite, SDK quality

Backend:

1. **`AnyTextValue` ToSql/FromSql wrapper**: vanilla `tokio_postgres`'s
   `ToSql for String` / `FromSql for String` reject `Kind::Enum` and
   `Kind::Domain` even though the wire format is plain UTF-8. The wrapper
   accepts those kinds in both directions. End result: explicit
   `$1::my_enum` / `CAST($1 AS my_enum)` casts now round-trip without the
   ugly `CAST($1::text AS my_enum)` workaround, AND `SELECT enum_col`
   results come back as JSON strings instead of erroring at the FromSql
   layer.

2. **#10 — Value::String → numeric/real/double/oid/bool**. Without these
   arms, a string-encoded value (`"3.14"`, `"true"`) for a non-text /
   non-temporal arg_t fell through to `Box<String> + TEXT`, which then
   failed at the server (no implicit cast text→numeric in expression
   context). Now strings are parsed into the matching native type with
   clear error messages on parse failure.

3. **Position-aware `$N` rewrite**: replaces the regex-based renumbering
   (which fixed the `$5/$50` substring collision but still walked through
   string literals and comments, mangling `'price: $5'` etc.) with a
   walk over `parse_pg_statement_arg_positions` — the same
   string/comment/dollar-quote-aware tokenizer used for index discovery.
   Adds `parse_pg_statement_arg_positions` to the parser's public API.

SDK:

4. **BigInt support**: `JSON.stringify(BigInt)` throws. The SDK now
   stringifies bigints before serialisation; the executor accepts
   numeric strings into BIGINT arg slots via the existing
   `Value::String → INT8` parsing arm. SDK-side `inferSqlType` is split
   so `BigInt` always resolves to `BIGINT` (was reaching
   `Number.isInteger(BigInt)` which returns false → wrong default).

5. **Homogeneous array auto-tag**: `${[1,2,3]}` against an `int[]` column
   now emits `$1::BIGINT[]` instead of `$1::JSON`. Detection covers
   primitive types only (number / bigint / string / boolean); mixed or
   nested arrays still fall back to JSON. Mixed int/float widens to
   `DOUBLE PRECISION[]`.

6. **`.query()` positional bug**: previously the `.query()` method
   abused the template-tag builder, which appended `$N::TYPE` after the
   user's literal SQL string instead of binding by position
   (`SELECT $1, $2` became `SELECT $1, $2$1::BIGINT`). Now `.query()`
   builds the executor-shaped content directly: a `-- $N argN (TYPE)`
   declaration block followed by the user's SQL verbatim.

Tests:

- Parser: `test_parse_pg_statement_arg_positions_skips_strings_and_comments`
  asserts string literals, comments, and dollar-quoted blocks don't
  produce positions (so renumbering doesn't mangle them).
- Executor unit: `renumber_sparse_placeholders_no_collision_no_string_mangling`
  uses the new position-aware path and includes string-literal + comment
  + `$$…$$` cases. Existing convert_val tests grow to cover new
  String→numeric/real/double/oid/bool arms.
- Integration: `test_postgresql_arg_type_combinations` adds 13 cases
  (enum round-trip both directions, string→numeric/real/double/bool/oid,
  string-literal `$N` non-mangling). The prepare-fallback test now
  asserts SUCCESS (not failure) for enum encoding via AnyTextValue.
- SDK: new `typescript-client/tests/sqlUtils.test.ts` (42 tests)
  exhaustively covering inferSqlType primitives + arrays,
  parseTypeAnnotation, datatable() template tag (with all the new
  shapes — BigInt, homogeneous arrays, RawSql, schema preamble),
  datatable().query() positional, and ducklake() shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pg): replace DISCARD ALL with curated reset (preserves typeinfo cache)

Found while exhaustively probing custom-type DX: every cached-connection
reuse was running `DISCARD ALL`, whose included `DEALLOCATE ALL`
deallocates *all* prepared statements server-side — including the typeinfo
statements that tokio_postgres caches per-Client to resolve custom enum /
domain Oids. tokio_postgres still held `Statement` objects whose names
the server had forgotten, so the next custom-type query failed with
intermittent "prepared statement \"sN\" does not exist" errors. The
failure was easy to reproduce: any sequence that forced typeinfo lookup
for two different custom-type kinds on the same cached connection (e.g.
enum followed by domain) would hit it.

Replace `DISCARD ALL` with a curated reset that explicitly targets the
state we actually care about, *without* touching prepared statements:

  RESET ALL                     — GUC parameters (search_path, application
                                  _name, statement_timeout, …)
  RESET SESSION AUTHORIZATION   — undoes both `SET SESSION AUTHORIZATION`
                                  and `SET ROLE` (RESET ALL does NOT —
                                  these aren't GUC parameters, so without
                                  this an elevated role from a previous
                                  job would silently leak)
  UNLISTEN *                    — drops LISTEN registrations
  CLOSE ALL                     — closes open cursors

Trade-off: temp tables, advisory locks (session-scoped), and user-created
PREPARE statements may persist across cached-connection reuse — rare in
datatable / PG-script workloads. tokio_postgres's typeinfo cache survives
intact, so custom enum / domain queries are fast on subsequent reuse.

Tests:
- `test_postgresql_custom_types_on_cached_connection` — runs 10×
  alternating enum + domain queries on a cached connection. Pre-fix this
  failed with `prepared statement "sN" does not exist` after the first
  reuse; post-fix passes.
- `test_postgresql_set_role_does_not_leak_across_cached_connection` —
  switches `SET ROLE` and `SET SESSION AUTHORIZATION` to a non-postgres
  role, then runs a follow-up job and asserts current_user/session_user
  are restored. Specifically catches the case where someone might switch
  back to `RESET ALL` alone (which doesn't cover SET ROLE / SESSION
  AUTHORIZATION) and silently introduce a permission-leak vector.
- All existing session-isolation tests
  (`test_postgresql_cached_connection_resets_session`,
   `test_postgresql_single_worker_session_isolation`,
   `test_postgresql_100_jobs_cached`) continue to pass.

Found via end-to-end probing of datatable / PG-script DX, not previously
covered: the existing isolation tests only did `SET ROLE postgres`, the
connecting user, so the leak was invisible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pg): address PR #8999 review (cubic + claude)

cubic (P1, real bug):
- `convert_vec_val` for `timetz` array asserted `Type::TIMETZ_ARRAY`, but
  chrono `NaiveTime` only encodes for TIME (same caveat as the scalar
  arm). Switch to `Type::TIME_ARRAY`; rely on PG's implicit `time→timetz`
  assignment cast at the column site. Add an explicit unit test.

claude (#1, silent failure → explicit error):
- `Bool` + explicit `(char)` / `(character)` decl previously silently
  bound BOOL, hoping the server would cast at the use site — but PG has
  no implicit `bool→char` and the resulting error
  ("operator does not exist: bool = char") was opaque. Now error at
  bind time with an actionable hint to use `bool` decl or pass the
  value as a "t"/"f" string.

claude (#2, asymmetry doc):
- Object/Array still coerce to text on `matches!(typ, Typ::Str(_))`
  (covers both explicit AND inferred-default text), unlike Bool/Number
  which key on `explicit_text_target`. The asymmetry is intentional
  (no implicit `jsonb → text` cast in expression context vs PG having
  implicit `bool/int → text` casts) — added a body comment so future
  maintainers don't try to "align" them.

claude (#3, perf):
- `parse_pg_statement_arg_indices` and `parse_pg_statement_arg_positions`
  walked the SQL tokenizer twice. Fold into a single pass that derives
  the index set from the position list.

claude (#4, fmt drift):
- `cargo fmt` over the parser crates I touched with perl scripts in the
  earlier commit (windmill-parser-{sql,bash,ts,go,php,java,csharp,nu,py,
  rust,graphql,yaml,r}). Net cosmetic.

claude (#5, parseTypeAnnotation):
- One-line caveat in the SDK's `parseTypeAnnotation` that the returned
  string is presence-only (e.g. `${x}::DOUBLE PRECISION` returns
  `"DOUBLE"`, `CAST(${x} AS int)` returns `"int)"` — neither matches a
  real PG type, but the only consumer just checks `!== undefined`).

While here — discovered + fixed independently while exhaustively probing
DX:

- **Replace `DISCARD ALL` with curated reset** (`RESET ALL; RESET
  SESSION AUTHORIZATION; UNLISTEN *; CLOSE ALL;`). DISCARD's
  `DEALLOCATE ALL` killed tokio_postgres' typeinfo cache, producing
  intermittent `prepared statement "sN" does not exist` errors on
  custom-type queries after cached-conn reuse. New regression tests:
  `test_postgresql_custom_types_on_cached_connection` and
  `test_postgresql_set_role_does_not_leak_across_cached_connection`
  (the latter catches the case where someone might switch back to
  `RESET ALL` alone and silently introduce a permission-leak vector —
  RESET ALL doesn't cover SET ROLE / SET SESSION AUTHORIZATION).

- **ISO-8601 timestamp results** (`pg_cell_to_json_value`). Pre-fix
  `TIMESTAMP` was rendered with a space separator ("2024-01-15 10:30:00")
  and `TIMESTAMPTZ` with " UTC" suffix ("2024-01-15 10:30:00 UTC") —
  neither parseable by `date-fns parseISO`, JavaScript `new Date()` is
  lenient enough to handle them but several frontend `App*Input.svelte`
  components use parseISO and fail silently. Switched to ISO-8601 with
  `T` separator and `+00:00` offset; arg-parsing path still accepts the
  legacy " UTC" suffix for back-compat.

Test coverage:
- 17/17 unit (`pg_executor::tests`)
- 9/9 integration (`backend/tests/worker.rs`, `test_postgresql_*`)
- 27/27 parser (`windmill-parser-sql`)
- 42/42 SDK (`typescript-client/tests/sqlUtils.test.ts`)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pg): bounded one-shot warning on numeric precision loss + ISO-8601 + NaN handling

Found while probing PG-script DX with millions of numeric cells:

1. **Numeric precision-loss warning**: `numeric` results are still serialised
   as JSON Number (back-compat — switching to JSON String would silently
   break user code doing arithmetic on results), but we now detect
   `Decimal -> f64 -> Decimal` round-trip failure and emit a single
   job-log warning recommending a `::text` cast in the SQL. Bounded by
   `NUMERIC_PRECISION_CHECK_BUDGET = 256` cells per query (one atomic
   load + one fetch_sub on the hot path; first lossy value
   short-circuits to a single load thereafter). Worst-case overhead on
   a 1M-cell numeric-heavy query: ~25µs of checks + 5ns × N atomic
   loads (vs. ~100ms unbounded).

2. **ISO-8601 timestamps**: `pg_cell_to_json_value` previously returned
   `"2024-01-15 10:30:00"` (TIMESTAMP) and `"2024-01-15 10:30:00 UTC"`
   (TIMESTAMPTZ) — neither parseable by date-fns `parseISO`, which is
   what the apps `App*Input.svelte` components use, so timestamp values
   silently failed to round-trip into date pickers. Switch to ISO-8601
   (`T` separator + `+00:00` offset) on the result side; arg-parser
   continues to accept the legacy `" UTC"`-suffixed format for
   back-compat.

3. **Float NaN / Infinity results**: `Number::from_f64` returns None for
   NaN / ±Inf, which `pg_cell_to_json_value` was raising as
   "invalid json-float" — failing the *entire* query if any cell held
   one of these special values. Now serialise them as JSON strings
   ("NaN", "Infinity", "-Infinity") and let the rest of the row come
   through. Arg-side: `s.parse::<f64>()` already accepts the same
   strings.

Tests:
- `decimal_fits_f64_losslessly_predicate` — covers fits / doesn't-fit
  cases for the precision-loss predicate.
- `precision_check_budget_caps_per_query_overhead` — locks in the
  budget cap and the loss-flag short-circuit.
- All 9 PG integration tests + 17 unit tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pg): add pg_advisory_unlock_all to reset; warn on missing args; honor decl defaults

While probing PG-script DX further found three more frictions:

1. **Advisory lock leak** (cubic P2): switching from `DISCARD ALL` to
   `RESET ALL; RESET SESSION AUTHORIZATION; UNLISTEN *; CLOSE ALL;`
   meant session-scoped advisory locks (`pg_advisory_lock`) leaked
   across cached-connection reuse. Add `SELECT pg_advisory_unlock_all()`
   to the chain — `DISCARD ALL` covered this implicitly via
   `DISCARD PLANS / DEALLOCATE / pg_advisory_unlock_all` and we lost it
   in the switch.

2. **Missing-arg silent NULL**: an arg declared in the SQL (e.g.
   `-- $1 amount (numeric)`) but not provided in the args object was
   bound as NULL with no error / warning. Misspelling the key in the
   args object silently produced a row of NULLs — a notorious DX
   debugging trap. Now: collect the names of declared-but-missing
   args during dispatch and emit a single one-shot warning to the job
   logs at end-of-query naming each one. Bound NULL is preserved for
   back-compat.

3. **Declaration defaults ignored**: `-- $1 a (int) = 5` carries
   `arg.default = Some(Number(5))`, but the dispatch fell straight to
   NULL when the arg was missing. Now: respect the default —
   user-supplied value > declaration default > NULL. Also fixes the
   warning logic above (only warn for args that *don't* have a default).

Tests: existing 19 unit + 9 integration pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pg): multi-word PG types with [] suffix lost the array-ness; array arms accept stringified values

Two more frictions found while probing SDK end-to-end against a real
datatable resource:

1. **Multi-word array types lose the [] suffix in the parser**.
   `transform_types_with_spaces` recognises aliases for "double
   precision", "character varying", "timestamp with time zone", etc.
   but its return type was `&'a str` — only the bare alias, never with
   a trailing `[]`. The `RE_CODE_PGSQL` regex's `\w+` captures stop at
   the first space, so the regex's own `(?:\[\])?` array-suffix branch
   sees only `"double"` (not `"double precision[]"`); the `[]` was
   silently lost. Result: `$1::double precision[]` (which the SDK now
   emits for homogeneous float arrays via the new auto-tag) routed
   through `Value::Array → Type::JSONB` and the server failed with
   "cannot cast type jsonb to double precision[]".

   Fix: switch `transform_types_with_spaces` to return `Cow<'a, str>`
   and re-check the trailing bytes after a multi-word match. If they
   start with `[]`, return `format!("{alias}[]")` — Owned. Single-word
   types and the no-match path keep returning Borrowed slices, so no
   allocation in the hot path.

2. **Array arms in `convert_vec_val` rejected stringified values for
   numeric / int* / bool / oid / real / double**. The scalar `convert_val`
   already parses strings into the matching native type for these arg_ts,
   but the array variant only accepted JSON-native counterparts. Sending
   `["1.5", "2.5", "3.5"]` against `$1::numeric[]` (e.g. via `unnest` for
   bulk loading, or `JSON.stringify(BigInt[])` round-trip) failed with
   "Mixed types in array". Now the array arms mirror the scalar ones —
   `as_<native>().or_else(|| as_str().and_then(parse))` — so both shapes
   round-trip cleanly.

Tests: 19 unit + 9 integration pass; existing parser tests cover the
multi-word array forms (the regex-cap behaviour didn't break for
single-word types, and Cow plumbing is transparent to all callers).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(parsers): add otyp_inferred field to Arg literals in tests + 3 missed src files

CI failures: the perl-driven sweep that added `otyp_inferred: false` to
every `Arg { ... }` literal when I introduced the field in the parser
schema covered `src/lib.rs` files but missed:

  - parsers/windmill-parser-bash/src/lib.rs       (mass-edited but a
    later format pass un-applied a few sites)
  - parsers/windmill-parser-go/src/lib.rs         (same)
  - parsers/windmill-parser-graphql/src/lib.rs    (same)
  - parsers/windmill-parser-nu/tests/tests.rs     (test file — not
    swept the first time)
  - parsers/windmill-parser-ts/tests/tests.rs     (test file — same)

Also tightened the regex to handle `oidx: None` without the trailing
comma (some test files had the field as the last initialiser line).

`cargo build --features <CI feature combo> --workspace --all-targets`
is clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sdk): Date → TIMESTAMPTZ; NaN / ±Infinity → string

Two more frictions found while running the actual SDK end-to-end against
a live datatable resource:

1. **JS `Date`** fell into the typeof "object" branch and was tagged
   `::JSON`. It worked accidentally for `${date}::timestamptz` via PG's
   `json → text → timestamptz` implicit cast chain, but `${date}` against
   a `timestamptz` column without a user-supplied cast bound the value
   as a JSON string and the comparison `timestamptz = json` failed. Now:
   `inferSqlType` recognises `Date` and tags `::TIMESTAMPTZ`;
   `serializeArgValue` emits `Date.toISOString()` so the executor's
   `Value::String → TIMESTAMPTZ` arm parses it cleanly.

2. **JS `NaN` / `±Infinity`** silently became NULL. `JSON.stringify(NaN)`
   returns `"null"` per the JS spec, so the value reached the executor as
   JSON null — the SDK's `::DOUBLE PRECISION` tag then bound a NULL
   double. Fix: detect non-finite numbers in `serializeArgValue` and
   stringify them as `"NaN" / "Infinity" / "-Infinity"`. The executor's
   `Value::String → FLOAT8` arm (`f64::from_str`) accepts these literals
   directly, and the result-side already renders the values as JSON
   strings (matching round-trip).

SDK unit tests grow from 42 → 44 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(pg): integration coverage for multi-word arrays + stringified array elements

Locks in the two array fixes from the previous commit
(`fix(pg): multi-word PG types with [] suffix lost the array-ness`)
with end-to-end cases in `test_postgresql_arg_type_combinations`:

- `double precision[]`, `character varying[]`, `timestamp without time
  zone[]` — verifies the parser keeps the `[]` suffix after multi-word
  alias resolution.
- `numeric[]` / `int[]` / `bool[]` from stringified primitives — verifies
  the array arms of `convert_vec_val` apply the same string-coercion
  the scalar arms do.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* style: fix indentation drift on otyp_inferred lines

cargo fmt cleanup of leftover indentation where the perl-driven sweep
that introduced the otyp_inferred field landed at the wrong column.
No behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-01 17:08:59 +00:00
hugocasa 9cb777a6b6 fix: use otel.status_message for OTLP Status.message on failed jobs (#8995)
tracing-opentelemetry only recognizes otel.status_code and
otel.status_message as fields that map to the OTLP Status proto.
The previously-used otel.status_description fell through to the
generic attribute recorder, leaving Status.message unset and
preventing OTLP consumers from filtering spans on error status.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:19:22 +00:00
Ruben Fiszel 96324ea5ae feat: ansible delegate_to_git_repo install_requirements, dynamic fields, --limit (#8997)
* feat: ansible delegate_to_git_repo install_requirements, dynamic fields, --limit

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: include .yaml variants in collections/roles requirements lookup

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-01 12:12:04 +00:00
Ruben Fiszel 7bdfc0ea06 fix: avoid named prepared statements in datatable/PG executor (#8988)
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>
2026-04-30 13:04:34 +00:00
Ruben Fiszel cec84849b9 feat: OTEL span status on failed jobs + Python stderr severity classification (#8918)
* 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>
2026-04-29 14:14:47 +00:00
hugocasa c0eeea9c83 feat: support S3Object input args in native SQL scripts (#8954)
* 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>
2026-04-28 20:01:06 +00:00
hugocasa c95642863e feat: support restart from steps inside BranchOne, ForLoop, Subflow (#8955)
* 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>
2026-04-28 20:00:03 +00:00
Ruben Fiszel 1d279e7a1e feat: add min release age instance settings for bun and uv (#8956)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-27 22:45:53 +00:00
Ruben Fiszel e636f589a5 fix: prevent flow-dep job stalls under row-lock contention (#8952)
* 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>
2026-04-27 16:03:57 +00:00
Ruben Fiszel 581658d881 fix(wac): recognize @workflow main, list WAC in scripts/list, run preprocessor (#8951)
* 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>
2026-04-27 13:38:49 +00:00
Ruben Fiszel e732004180 fix(nativets): forward OTEL-prefixed console logs to tracing events (#8937)
* 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>
2026-04-24 19:42:05 +00:00
hugocasa 8a986500b9 feat: WM_TESTED_RUNNABLE env var + wildcards in test: annotation (#8926)
* 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>
2026-04-24 12:52:25 +00:00
centdix 9a60ff2e77 feat: add ai agent conversation output control (#8915)
* 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>
2026-04-23 14:13:20 +00:00
centdix eeb5d12be3 fix: support windmill chat answer override (#8909)
* 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>
2026-04-22 17:27:10 +00:00
Ruben Fiszel dc896737ac fix: apply powershell workspace dependencies to deployed scripts (#8912)
* 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>
2026-04-22 15:44:00 +00:00
hugocasa dffb89e006 fix: trigger failure_module when branchone predicate throws (#8905)
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
2026-04-22 14:46:21 +00:00
centdix 26a6d1e4ce refactor: create windmill-ai crate (part 1 — types, traits, base modules) (#8530)
* 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>
2026-04-22 11:20:27 +00:00
hugocasa 53badf1a8c fix: track dollar-quoted strings in SQL block splitter (#8891)
* 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>
2026-04-21 17:07:01 +00:00
hugocasa 2d4fadb590 feat: add s3 stream progress logs to other DB executors (#8898)
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>
2026-04-21 17:06:42 +00:00
hugocasa f8c916cb60 fix: rust nsjail RUSTUP_HOME mount and arch-aware cache keys (#8890)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 05:21:22 -04:00
hugocasa 43a6b57581 perf: speed up mssql s3 ingest and add phase logs to job output (#8884)
* 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>
2026-04-20 18:20:02 +00:00
Ruben Fiszel 94f27af838 fix: log boolean predicate eval errors to root flow logs (#8885)
* 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>
2026-04-20 18:11:07 +00:00
hugocasa 172a7d16db fix: fix otel tracing on nativets
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-17 14:05:58 +00:00
centdix b1778272fc fix: clean ai memory and cache bedrock prompts (#8847)
* 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>
2026-04-16 14:30:22 +00:00
hugocasa 4bda600729 fix: skip nsjail uidmap/gidmap when DISABLE_NUSER=true (#8842)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 05:30:57 -04:00