Commit Graph

14004 Commits

Author SHA1 Message Date
Ruben Fiszel 2caee41fdf fix(worker): mount /dev/shm as tmpfs in the Docker v2 nsjail sandbox (#10240)
The Docker v2 nsjail profile provided /dev/null, /dev/zero, /dev/random,
and /dev/urandom but omitted /dev/shm, since generate_rootfs_mounts()
skips the image's own /dev in favor of the profile's device nodes. Any
program needing POSIX shared memory (Ansible/Python multiprocessing,
Chromium) failed with "No such file or directory: /dev/shm".

Add a /dev/shm tmpfs mount, matching run.ansible.config.proto and
run.python3.config.proto.

Fixes WIN-2216

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:49:29 +00:00
Ruben Fiszel 68debab877 feat(triggers): add AMQP (RabbitMQ) trigger via lapin (#10230)
* feat(triggers): add AMQP (RabbitMQ) trigger using the lapin library

Fixes WIN-2214

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

* chore(triggers): defer AMQP cross-workspace deploy pending utils-internal publish

Revert the amqp_trigger additions to the shared windmill-utils-internal
TriggerDeployKind and the frontend cross-workspace deploy adapter: the
frontend installs the published npm package, which lacks the new kind
until a release is cut. AMQP create/edit/delete/list/sync/capture are
unaffected (they use local types); only cross-workspace deploy/merge of
AMQP triggers waits on the package bump. Also document the at-most-once
ack in the consumer loop.

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

* fix(triggers): address AMQP review — at-least-once ack, workspace cascade, contracts

- ack AMQP deliveries only after successful dispatch; nack+requeue on failure
- add ON DELETE CASCADE workspace FK so amqp_trigger rows are cleaned on
  workspace deletion (and the listener stops)
- fix the /amqp_triggers/test OpenAPI body and add amqp_trigger to
  WorkspaceDiffRow.kind
- register AMQP in the generated workspace trigger tool (create_trigger)
- drop banned $bindable defaults on optional props in the config section
- add build_uri unit tests (encoding, ports, vhost)

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

* fix(triggers): stop AMQP poison-message loop and reconnect on transient drops

Chaos testing against a live RabbitMQ broker showed the previous
nack(requeue) + immediate re-poll spun a tight redelivery loop (~1000
critical-error reports/sec) on a poison message, and any connection blip
permanently disabled the trigger (lapin has no built-in reconnect).

- on dispatch failure: nack+requeue then stop consuming; the listener
  framework re-lists the trigger after its ping goes stale (~15s), backing
  redelivery off to that cadence instead of a tight loop (verified: rate
  dropped from ~1000/s to ~1 per ~26s, message preserved)
- on connection/stream error: stop and let the framework reconnect instead
  of disabling; persistent failures are still disabled via get_consumer
  (verified: a forced connection close now auto-reconnects and resumes)
- finish the AI create-trigger action wiring for AMQP: add amqp to
  CreatedResourceTriggerKind, the action-card registry, and the drawer
  registry so the result card renders and its "Open" action works

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

* fix(triggers): complete AMQP frontend registries and defer merge rows

- add amqp to capturableTriggerTypes (so AmqpCapture mounts), the Runs
  jobTriggerKinds filter, and CLOUD_DISABLED_TRIGGER_TYPES
- wire AMQP into global AI chat mode: TRIGGER_KINDS, the request union,
  writeTriggerSchema, triggerServices, and the draft adapter
- stop emitting actionable AMQP fork-comparison rows (revert amqp_trigger
  from TRIGGER_OR_SCHEDULE_TABLES) since cross-workspace deploy is deferred
  until windmill-utils-internal is published — avoids a deploy that fails
  with "Unknown kind: amqp_trigger"
- use design-system TextInput instead of raw <input> in the config section

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

* fix(triggers): complete AMQP session/draft registries and constrain prefetch

- add amqp to the session-deploy, draft-compare, preview-router, and
  copilot workspace-item registries so AMQP drafts/deploys/nav/path
  resolution work
- include amqp_count in the MoveDrawer attached-trigger rename warning
- replace the raw prefetch <input> with a design-system TextInput bounded
  to an integer 1-65535 (backend u16) and block save on invalid values

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

* fix(triggers): make AMQP disconnect/reconnect consistent with the Kafka trigger

lapin, like rdkafka, has no transparent reconnect, so the AMQP listener now
mirrors the Kafka trigger's explicit reconnect loop instead of relying on the
framework re-list (which disabled the trigger once get_consumer failed on a
sustained outage):

- get_consumer returns cheaply; consume owns a (re)connect loop that retries
  with a 30s backoff, reports a critical error every 10 failed attempts, and
  reports a recovered critical error once it reconnects — never disabling the
  trigger on a connectivity failure
- a consumer/stream error breaks out to reconnect rather than disabling
- dispatch failure still nacks+requeues (at-least-once) with a short backoff
  to avoid a tight poison-message loop, keeping the connection alive

Verified against a live RabbitMQ broker: killing the broker keeps the trigger
enabled and retrying (attempt N), and restarting it auto-reconnects (logs
"reconnected after N attempts") and resumes dispatch.

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

* fix(triggers): complete AMQP capture registries and constrain prefetch contract

- add the 'amqp' case to triggerKindToTriggerType so opening the AMQP editor
  from a capture button no longer throws "Unknown TriggerKind: amqp"
- register AmqpIcon in CaptureTable's icon map and add an AMQP entry to the
  script/flow CaptureButton menu
- bound the OpenAPI prefetch_count to an integer 1-65535 (matches the Rust
  u16) and regenerate clients/prompts
- require a non-empty exchange name when the exchange binding is enabled
- build_uri: fall back to "/" on a blank vhost and bracket IPv6 hosts (+ tests)

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

* feat(triggers): wire AMQP into pipeline graph, git-sync, and preprocessor types

- asset_graph: discover attached amqp_trigger rows and emit an AMQP TriggerEdge
  so AMQP triggers render (and can be opened/deleted) on the data-pipeline canvas
- frontend pipeline graph: add amqp to NativeTriggerKind, the add-trigger menu,
  node presentation, event-trigger set, annotation keywords, and the
  editor/service registrations
- git-sync: add the amqp_trigger include pattern (+ test) so an AMQP git-sync
  deployment stages only its .amqp_trigger.* file, not an unrelated same-path object
- preprocessor starters: add the AMQP event to the generated TS/Python/PHP
  trigger event types (kind/payload/exchange/routing_key/queue_name/redelivered/
  delivery_tag)

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

* fix(triggers): finish AMQP pipeline/parser wiring, prefetch validation, source lists

- fix a stray edit that corrupted the pre-existing MqttTriggerEditor import
  ($lib/... path) in PipelineTriggerEditors.svelte
- reject prefetch_count = 0 server-side in validate_config (RabbitMQ treats 0
  as unlimited) and defensively skip basic_qos(0) in build_consumer (covers
  the capture path that bypasses CRUD validation)
- recognize `// on amqp` in the canonical parser (TriggerSpec::Amqp) and add
  amqp to the CLI non-autorun/event-trigger sets so a pipeline cascade never
  runs an AMQP-only node as a manual root without an event
- add amqp to the preprocessor intro lists and both pipeline AI instructions

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

* fix(triggers): reject zero AMQP prefetch in all paths and finish guidance lists

- extract a shared validate_amqp_options used by both CRUD validate_config
  and build_consumer, so capture configs (which bypass CRUD validation) also
  reject prefetch 0 instead of silently connecting with an unlimited buffer
  (+ unit tests for 0/1/65535/None)
- add AMQP to the main script-writing preprocessor-sources prompt and the CLI
  triggers-skill guidance list

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

* docs(triggers): de-duplicate AMQP prefetch comment and fix GET response text

- keep the zero-prefetch rationale only on the shared validate_amqp_options
  doc; drop the redundant call-site comments
- correct the getAmqpTrigger OpenAPI 200 description ("deleted" -> "retrieved")

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

* chore: update ee-repo-ref to aaa6cb89b05b76139252c64f057e53b94d12ac60

This commit updates the EE repository reference after PR #680 was merged in windmill-ee-private.

Previous ee-repo-ref: 5da5fd65aca9594b2611837a52e4677b544b0380

New ee-repo-ref: aaa6cb89b05b76139252c64f057e53b94d12ac60

Automated by sync-ee-ref workflow.

* chore(migrations): consolidate the four AMQP migrations into one

The table and the three enum ADD VALUE statements (trigger_kind, job_trigger_kind,
draft_kind) are one atomic feature. ALTER TYPE ... ADD VALUE runs inside the
migration transaction on PG >= 14 (Windmill's minimum) since the amqp_trigger
table doesn't reference those enum types, so they can share a single migration
instead of four. Verified applying cleanly in a single transaction on a fresh DB.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-07-21 15:10:29 +00:00
hugocasa c7ee192680 update ee ref to git sync cli prompt fix (#10224) 2026-07-21 14:37:38 +00:00
Ruben Fiszel c6fd6da971 repoint slack hubPaths to form-data 4.0.6 versions (#10239)
The image prebundles the hub scripts listed in hubPaths.json by their
pinned (immutable) version. windmill-integrations republished the four
image-cached slack scripts with form-data bumped 4.0.5 -> 4.0.6; point
hubPaths.json at the new versions so freshly built images cache the fixed
lock instead of the stale one.

Fixes part of #10219

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 14:37:10 +00:00
Diego Imbert d24e176816 fix(parser): spurious pg arg inferred from placeholders in comments (#10226)
* fix(parser): ignore pg placeholders in comments, strings and dollar quotes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NXWRDKzswMeDBP5THXpb9Q

* package json + lock

* chore: remove stray root npm lockfiles

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kuUhCPD9ph4ZuFd13SCWJ

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:36:44 +00:00
Diego Imbert b0bf25683b fix(frontend): prevent browser back-swipe navigation over monaco editors (#10229)
* fix(frontend): prevent browser back-swipe navigation over monaco editors

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEP1T4UogYABBifdNJUUuQ

* fix: toggle root overscroll-behavior-x on editor hover instead of cancelling wheel events

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEP1T4UogYABBifdNJUUuQ

* refactor: replace per-editor swipe guard action with single global handler in root layout

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEP1T4UogYABBifdNJUUuQ

* style: compress swipe-guard comment to four lines

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q97WNWUT9mntEEVhg6kpzv

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:36:33 +00:00
Diego Imbert 32994df427 fix: return to parent workspace when a fork is deleted remotely (#9898)
* fix: return to parent workspace when a fork is deleted remotely

When a workspace fork was deleted remotely while a user had it open,
reloading stranded them: a regular member got logged out (whoami fails on
the vanished workspace) and a superadmin silently landed on a dead
workspace whose requests 404. Detect the deleted fork on load and redirect
to its parent (or the workspace picker) with a toast instead.

- forkParentMemory.ts: persist a bounded fork->parent map in localStorage
  while a fork is reachable (the parent is unrecoverable post-deletion).
- (logged) layout: record the current fork's parent via an effect.
- root layout: tryRecoverFromDeletedFork detects the vanished fork in
  loadUser and redirects to the remembered parent or the workspace picker,
  reusing the workspace list already fetched on mount.

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

* fix: verify fork existence for superadmins before deleted-fork redirect

* fix: only recover deleted fork on actual 404 from superadmin check

* fix: recover prefixless dev-workspace forks via remembered parent

* fix: use workspace exists check to detect deleted forks

* fix: restore wm-fork- detection for non-member superadmin forks

* fix: record fork parent for non-member superadmin dev workspaces

* Update frontend/src/routes/(root)/+layout.svelte

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* fix: store fork parent map with null-prototype to handle __proto__ ids

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-07-21 14:35:59 +00:00
Guilhem 9bc1f62128 feat(ai): session chat nits — empty sends, command picker polish, session-state prompt (#10233)
* feat(ai): session chat nits: empty sends, picker polish, session state

* fix(ai): scope empty-send turns to global chat and pin new behavior

* fix(ai): align grouped search nav with display order, enable empty-send button

* fix(ai): fork fallback for unlisted workspaces, section headers across branches

* style(ai): hint-colored 3xs picker section headers, drop inline row descriptions

* style(ai): more spacing between picker sections

* fix(ai): require context elements for empty global-chat sends

* style(ai): no empty bubble for text-free messages

* fix(ai): review round 2 — keyboard tooltip access, requestedMode guard, no display names in prompt

* fix(ai): queue context-only drafts pressed while a response streams

* fix(ai): shared context identity for queued badges and full queue-context union
2026-07-21 14:01:44 +00:00
Ruben Fiszel 39b9a9d3d5 bump slim image Python to 3.12.13 to ship current pip (#10236)
* fix(docker): upgrade bundled pip in uv-managed Python runtimes

uv-managed Python runtimes ship a pip that lags behind, and it gets baked
into the full, slim, and EE slim images at build time. Add a pinned
PIP_VERSION build arg and upgrade pip in every real managed runtime after
`uv python install`, failing the build if any upgrade fails.

Fixes part of #10219

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

* fix(docker): simplify pip upgrade to native uv per-version install

Replace the runtime-discovery loop with a single
`uv pip install --python <ver> --system --break-system-packages --upgrade`
per known Python version. uv resolves the managed interpreter by version,
so no directory scan or symlink handling is needed; a plain RUN still fails
the build if the pinned pip can't be installed.

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

* fix(docker): bump slim image Python patch to 3.12.13 for current pip

The full image floats LATEST_STABLE_PY to 3.12 (→ 3.12.13), whose
uv/python-build-standalone runtime already ships pip 26.1.2. The slim and
EE-slim images pinned 3.12.12, which ships pip 26.0.1 — the only runtime
actually behind. Align them to 3.12.13 so they inherit the current pip
natively, instead of adding an explicit pip-upgrade build step.

This supersedes the earlier pip-upgrade approach on this branch: the full
image needed no change, and the slim gap was purely the stale patch pin.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 13:38:21 +00:00
Guilhem 572d69e5ae feat(ai): open the Compare & Deploy page from chat with item preselection (#10232)
* feat(ai): open the Compare & Deploy page from chat with item preselection

* fix(ai): label the compare link card outside sessions

* fix(ai): scope untracked-chat compare links to explicit items

* style: drop narration comment on compare mask precedence

* fix(ai): match compare items mask against parked live-draft paths

* fix(ai): land maskless-mode compare on the view holding the masked drafts

* fix(ai): honor explicit fork mode over the draft-mask heuristic

* docs(ai): describe mask-aware compare mode auto-pick

* fix(ai): match legacy app fork diffs under their identity mask key
2026-07-21 12:58:18 +00:00
Ruben Fiszel 28966bdbf1 fix(frontend): curl fallback for +Variable/+Resource in bash sandbox mode (#10235)
* fix(frontend): use curl fallback for +Variable/+Resource in bash sandbox mode

When a bash script uses `# sandbox <image>` or `# docker`, the body runs
inside a custom container image that does not have the `wmill` CLI
installed, so the `wmill variable get` / `wmill resource get` snippets
inserted by the +Variable and +Resource pickers fail.

Detect `# sandbox`/`# docker` in the editor code and insert a curl-based
snippet using the BASE_INTERNAL_URL, WM_TOKEN and WM_WORKSPACE env vars
(available in sandbox) instead.

Fixes WIN-2215

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

* fix(frontend): mirror worker grammar and use curl/wget fallback in sandbox mode

Address review of the bash sandbox picker fallback:

- Extract detection into `bashRunsInCustomImage`, mirroring the worker's
  BashAnnotations grammar (leading comment lines only; `# sandbox <image>`
  or bare `# docker`). A bare `# sandbox` is the nsjail-bash modifier that
  still runs on the worker rootfs where `wmill` is available, so it now
  correctly keeps the `wmill` snippet. This also fixes the substring
  false positives (`# sandboxed`, `# docker` in prose/body) and false
  negatives (`#sandbox <image>`).
- The default `# sandbox alpine:latest` image ships busybox `wget`, not
  `curl`, so the snippet now tries `curl` then falls back to `wget`.
- `variables/get_value` returns a JSON-quoted string; strip the outer
  quotes with `sed` so the sandbox snippet matches the `jq -r .value`
  output of the non-sandbox branch. Resources return JSON either way.
- Add focused unit tests for the detection grammar.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:53:52 +00:00
Ruben Fiszel 6e42633643 fix(pg): actionable error when s3object input exceeds jsonb 256MB cap (#10228)
Native SQL PostgreSQL scripts with an `(s3object)` input materialize the
whole referenced file into a single jsonb parameter. PostgreSQL hard-caps a
jsonb value's element payload at 256MB, so a large file fails with an opaque
`total size of jsonb array elements exceeds the maximum of 268435455 bytes`.

`materialize_s3object_args` now reports the largest materialized payload, and
that specific server error is rewritten into guidance explaining the input is
materialized (not streamed) and pointing large-file users at DuckDB, which
reads S3 natively and streams.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:21:34 +00:00
Ruben Fiszel 39058c0a01 fix(postgres-triggers): enforce resource-path scopes on ancillary routes (#10222)
* fix(postgres-triggers): enforce resource-path scopes on slot/publication routes

The Postgres trigger ancillary routes (slot management, publication
management, version/logical-level checks, template script creation) relied
only on the route-level middleware, which validates the scope domain+action
but delegates resource-path enforcement to each handler. These handlers made
no check_scopes call, so a token scoped to one postgres resource path (e.g.
postgres_triggers:write:u/alice/*) could drive these endpoints against any
postgres resource in the workspace, including the destructive drop_slot_name
(pg_terminate_backend + pg_drop_replication_slot).

Add a check_scopes call at the top of each affected handler, before any
connection is opened, mirroring the generic trigger CRUD handlers. Read
endpoints require postgres_triggers:read:{path}; write endpoints require
postgres_triggers:write:{path}.

Fixes WIN-2213

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

* docs: condense postgres_trigger_scope test module comment

Address Codex nit: state the durable constraint (mismatched scope must fail
before DB access) instead of narrating pre-fix behavior and change history.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 09:21:07 +00:00
Ruben Fiszel fd51d40f12 feat(pipelines): catalog declared measures and dimensions (#10190) 2026-07-21 07:34:44 +02:00
hugocasa adc555d172 fix(triggers): apply scope-path filtering to list and fix update scope check (#10220)
The shared `list_triggers<T>` handler returned every trigger row of its
type in the workspace regardless of the token's declared scope. A token
limited to e.g. `http_triggers:read:<prefix>/*` could enumerate all
trigger paths (and their configs) through the `/list` endpoint, while
`get_trigger`, `create_trigger`, `delete_trigger` and `exists_trigger`
correctly rejected them. This affected all 10 TriggerCrud kinds (HTTP,
WebSocket, Kafka, NATS, MQTT, SQS, GCP, Azure, Postgres, Email).

Apply `build_scope_path_predicate(&authed, T::scope_domain_name(),
"read")` to the returned rows after the draft-only append, mirroring
scripts, flows, apps, resources, variables and schedules. A `HasPath`
supertrait on `Self::Trigger` exposes the row path to the shared handler
without each impl restating it (`Trigger<T>` returns `&base.path`, the
`()` OSS stub returns "").

Also fix `update_trigger`: it only checked scope against the new path in
the request body, letting a scoped token move a trigger it can't touch
into its scope. Now check both the existing path (URL) and the new path.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 07:09:31 +02:00
Ruben Fiszel 7ec56ef619 fix: require auth on GET /concurrency_groups/{job_id}/key (#10221)
The /concurrency_groups global service is registered after the router's
.route_layer(from_extractor::<ApiAuthed>()), and Axum's route_layer only
wraps routes present at the call site, so this handler was left with no
router-level auth. Unlike its siblings list_concurrency_groups and
prune_concurrency_group, get_concurrency_key declared no ApiAuthed
parameter, so GET /api/concurrency_groups/{job_id}/key was reachable
unauthenticated and would return concurrency keys (which can embed
workspace ids, script/flow paths, and $args-templated argument values)
for arbitrary jobs instance-wide.

Add _authed: ApiAuthed so the extractor independently enforces
authentication regardless of layer placement, matching the
defense-in-depth pattern of the sibling handlers.

Fixes WIN-2212

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 07:08:16 +02:00
Ruben Fiszel 858d9a5527 chore(main): release 1.764.0 (#10196)
* chore(main): release 1.764.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.764.0
2026-07-20 22:56:04 +02:00
Ruben Fiszel ddec2abbb3 feat(jobs): cap total queued jobs per workspace on cloud (#10218)
* feat(jobs): cap total queued jobs per workspace on cloud

A workspace could flood the queue with an unbounded number of jobs across
many concurrency keys and scripts (or keyless jobs), which the per-key
cap from #10197 does not bound. Add a companion instance-wide ceiling on
a workspace's total queued jobs.

check_workspace_queue_cap rejects a push once the workspace has
WORKSPACE_MAX_QUEUED_JOBS (default 20000, superadmin-configurable, 0 to
disable) jobs queued, cloud-only and runtime-gated on CLOUD_HOSTED like
the per-key cap. It runs on every push, so it applies even to premium
workspaces and catches parallel for-loop floods. Jobs already queued
still drain; only new pushes past the ceiling are rejected, so an
in-flight flow only fails to push further work while at the ceiling.

The setting loader self-gates on CLOUD_HOSTED so it is never loaded off
cloud, from initial load or a settings-change reload. The depth count is
bounded by the cap via LIMIT so a runaway backlog never costs an
unbounded scan on the push path.

* docs(jobs): note the workspace cap is a soft ceiling and the depth helper is count-only

Records the two review points as constraints: the cap does not serialize
admission (a soft ceiling by design, like the per-key cap), and
workspace_queue_depth is pub only for the test, returns a count not job
data, and leaves authorization to the caller.
2026-07-20 22:09:59 +02:00
hugocasa b070f56c5e feat: detect server-handled git-sync so CLI picks git push vs wmill sync push (#10201)
* feat: detect server-handled git-sync so CLI picks git push vs wmill sync push

Add a non-admin GET /w/{w}/workspaces/git_sync_deploy_mode endpoint returning
{configured, deploy_on_push}, so any workspace member (not just admins, who
alone can read get_settings) can tell whether pushing to the git remote deploys
via server-side auto-pull. Surface it through `wmill gitsync-settings status`
and align the deploy guidance/skills to prefer git push when the repo deploys on
push, falling back to `wmill sync push` otherwise.

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

* fix: address review — clean JSON output, warn on git_sync parse failure

- gitsync-settings status --json-output now uses console.log so the JSON pipes
  cleanly to jq (log.info wraps it in ANSI color codes)
- get_git_sync_deploy_mode logs a warning on git_sync deserialize failure instead
  of silently reporting configured=false, and documents why it is not EE-gated

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

* fix: address codex review — license-gate and branch-match deploy-on-push

- get_git_sync_deploy_mode now reports deploy_on_push only on Enterprise-licensed
  instances (auto-pull can't run on CE/downgrade) and returns auto_pull_branches
  so the client knows which tracked branches actually deploy on push
- gitsync-settings status matches the local git branch against auto_pull_branches
  before recommending git push, so an untracked branch falls back to wmill sync push
- add an integration assertion for the endpoint's default (no git-sync) shape

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

* docs: point per-topic skills at the canonical Deploying section

The git-push-vs-wmill-sync-push decision lives in core.ts (AGENTS.wmill.md),
which is already in context. Have the per-topic skills reference the Deploying
section instead of re-encoding the detection, so there is one source of truth
and no drift (the compressed version also wrongly implied `gitsync-settings
status` detects the CI-workflow path, which only core.ts's filesystem check does).

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

* fix: match remote+branch server-side for deploy-on-push detection

Codex flagged that a workspace-level auto-pull signal recommends `git push` even
when the local checkout is a different repo/branch than the one that auto-deploys.
Match precisely instead, without exposing anything sensitive:

- git_sync_deploy_mode takes optional remote+branch query params. The backend
  normalizes each auto-pull repo's URL to host/path (dropping embedded
  user:token credentials by rebuilding from parsed components, never scrubbing
  the string) and compares to the caller's remote; deploy_on_push is true only on
  a licensed instance where an auto-pull repo matches that remote and tracked
  branch. The response is two booleans — no repo URLs or branches leave the server.
- Branchless (default-branch) and fork/sync_forks repos stay a safe fallback to
  `wmill sync push` rather than a wrong git-push recommendation.
- CLI status sends `git remote get-url` + current branch (new getGitRemoteUrl
  helper, --remote flag) and reports the matched result.
- Unit-test the URL normalization/credential-stripping directly, since a
  regression there would be a token-handling bug.

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

* fix: address codex security findings in deploy-mode detection

- Strip credentials from the local remote client-side before sending, so a
  token embedded in the URL never reaches the server's request-URI logs
- Fetch the remote via spawnSync arg array (not an interpolated shell string),
  removing a command-injection path from a caller-supplied --remote value
- Use the remote's push URL (`git remote get-url --push`) and recommend the
  qualified `git push <remote> <branch>`, so the pushed target matches the one
  the server checked
- Keep the port in remote normalization so different services on the same host
  don't collide into a false match
- Unit-test credential stripping (CLI) and port distinctness (backend)

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

* feat: resolve $var repos and fork sync_forks in deploy-mode detection

- Interpolate $var:/$res: references in the repo url/branch the same way the
  auto-pull poller does (system context, cached, only when a field is a
  reference), so variable-backed git URLs match instead of falling through
- For a fork workspace, evaluate the root ancestor's git-sync settings and treat
  its wm-fork/<base>/<id> branch as deploying when the root repo has
  auto_pull.enabled && sync_forks and its base matches the tracked branch
- Read settings/resources on the plain pool (a fork member may not belong to the
  root workspace); only booleans are returned
- Unit-test the fork/branch matching (base + sync_forks + workspace-id suffix)

A blank tracked branch (repo default) still needs a network ls-remote to resolve,
so it stays a safe fallback to `wmill sync push` rather than a wrong git push.

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

* refactor: share one git-repo resolver between poller and deploy-mode

The auto-pull poller and the deploy-mode endpoint both resolved a git-sync repo
resource (system context, $var:/$res: interpolation) with duplicated boilerplate.
Extract windmill_store::resources::resolve_git_repository_resource and have both
call it, so the interpolation lives in one place. Drops the endpoint's local
resolve_repo_url_branch helper and its raw SQL query (and cache entry).

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

* fix: address codex review — fork false-positives, shell-safety, auth contract

- Fork deploy detection now mirrors reconcile_fork_branch_pull: the wm-fork branch
  must route to this workspace (first existing of the id candidates) and the repo
  must be in the fork's own inherited settings, so a multi-repo root or an
  ambiguous id can't produce a false deploy_on_push
- Recommended deploy command is shell-quoted (branch/remote names may contain
  metacharacters and the output is agent-executed)
- Remote normalization folds only the host; repo paths stay case-sensitive
- Document the system/RLS-bypassing contract on the shared resolve helper and
  restore the head-fetch doc; fix the overclaiming integration-test comment
- Dev-workspace label and default-branch cases remain documented safe fallbacks

Also restores 5 sqlx cache entries an earlier cleanup dropped.

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

* fix: require a runnable auto-pull delivery path for deploy_on_push

enabled auto-pull alone doesn't mean a push deploys: a webhook-only repo with no
active hook (failed registration), or a repo that only polling could serve on an
SSH URL (the poller rejects SSH), delivers nothing. Gate deploy_on_push on an
actual delivery path — active webhook, or a pollable non-app HTTPS repo — per the
repo's auto-pull mode. Unit-tested across modes/webhook/URL-scheme/app.

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

* fix: read fresh repo config for on-demand deploy-mode detection

resolve_git_repository_resource took an implicit allow_cache=true (right for the
poller loop). An on-demand status could then match against a stale url/branch
cached by an earlier poll. Make allow_cache a parameter: poller keeps true, the
deploy-mode endpoint passes false so it reflects the current git-sync config.

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

* fix: defer the deploy fallback instead of assuming wmill sync push

When backend auto-pull doesn't match the checkout, `status` no longer flatly
recommends `wmill sync push` — a CI workflow may still deploy on push. It now
reports the backend signal and points at the Deploying guidance (check CI → git
push, else wmill sync push; record the choice as a `Deploy mode:` line in
AGENTS.md). deploy_command is null in JSON when undetermined. This resolves the
CI-backed false recommendation without the CLI re-implementing CI detection.

Also fix two review nits: restore the deploys_on_push_branch doc comment (it had
drifted onto has_runnable_delivery) and correct the app-repo comment (their
exclusion from the poll path is a conservative safe under-report, not "can't be
polled").

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

* refactor: drop the ambiguous-fork-id disambiguation from deploy-mode

The existence-query resolution guarded a very narrow case (a suffix owned by both
a coexisting wm-fork-<suffix> and <suffix> workspace, queried from the wrong one).
Not worth the per-fork query; keep the cheap candidate-family check plus the
inherited-repo membership test, which already close the real fork false-positive.

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

* refactor: drop remote-URL matching; disambiguate deploy-mode by repo count

Matching the caller's git remote URL against each repo dragged in the whole
remote-URL surface (sending the URL, credential stripping, shell-safe remote
handling, fetch/push URL, port/case normalization) — and the risk that came with
it. Replace it with a simpler rule that fits the actual question:

- deploy_on_push is true only when exactly ONE licensed, deliverable auto-pull
  repo tracks the pushed branch. With a single synced repo the local checkout is
  unambiguously it; with several we can't tell which is the caller's, so we
  return false and the CLI asks the user.
- The endpoint takes only `branch` (no `remote`); status no longer reads or
  sends the git remote.
- On the fallback, status now tells the agent to ASK the user how the repo
  deploys (CI git-push vs wmill sync push) and record it in AGENTS.md, instead of
  assuming wmill sync push. Guidance updated to match.

Removes normalize_git_remote (+url dep), getGitRemoteUrl, stripGitRemoteCredentials,
shellQuote, the --remote flag, and their tests.

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

* refactor: share fork-branch routing between reconciler and deploy-mode

Deploy-mode detection was re-deriving fork/dev routing (root walk, wm-fork/dev
branch parsing, descendant resolution, inherited-repo check) that the auto-pull
reconciler already owns — the source of repeated edge-case bugs. Extract it into
windmill_common::workspaces::resolve_fork_branch_target and have both the endpoint
and reconcile_fork_branch_pull (EE) call it, so they can't drift and dev
workspaces are handled by construction.

Endpoint now resolves the root via the canonical cached fork_ancestor_chain
(dropping a duplicate CTE) and routes forks/dev workspaces through the shared
resolver. The .sqlx cache is unchanged (the moved queries already existed).

Bumps ee-repo-ref for windmill-labs/windmill-ee-private companion.

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

* fix: exclude archived roots and frame deploy_on_push:false as unconfirmed

- deploy_on_push now requires the root workspace to be live; polling and webhook
  delivery both exclude deleted roots, so an archived root (or anything beneath
  one) with retained git-sync no longer reports deployable
- status and the OpenAPI now describe false as "not confirmed" (it also covers
  ambiguity and conservative false-negatives), not a definite no — the CLI asks
  the user rather than asserting the push won't deploy

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

* chore: bump ee-repo-ref for EE branch merge of main

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:44:24 +02:00
hugocasa 11fda89b52 feat(telemetry): generic feature-usage telemetry with AI session metrics (#10200)
* feat(telemetry): add generic feature_usage table and batched logging endpoint

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(telemetry): log AI session usage events and document them in telemetry settings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(telemetry): use escape sequence instead of literal NUL bytes in buffer key

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(telemetry): validate dimensions, decouple retention, keepalive flush

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(telemetry): allowlist feature-usage dimensions and index retention scans

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(telemetry): pin tool-name allowlist and deploy session attribution

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(telemetry): route AI chat usage through feature_usage and drop ai_chat_usage

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(telemetry): slim dimension validation to registered kinds plus key shape

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(telemetry): backfill ai_chat_usage into feature_usage before dropping it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(telemetry): disclose provider and model identifiers in telemetry settings text

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(telemetry): issue all flush chunks before awaiting so pagehide keeps them

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: update ee-repo-ref to 6306c072a50937ea9af44a5bcf42345543207486

This commit updates the EE repository reference after PR #672 was merged in windmill-ee-private.

Previous ee-repo-ref: 964f242a0eb44db7f7d26636cc8d76aeabea2b73

New ee-repo-ref: 6306c072a50937ea9af44a5bcf42345543207486

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-07-20 20:56:57 +02:00
windmill-internal-app[bot] f635bd5ae7 chore: refresh vendored docs snapshot (#10068)
Co-authored-by: hugocasa <15649739+hugocasa@users.noreply.github.com>
2026-07-20 20:43:03 +02:00
Ruben Fiszel 11bb37d7ca fix(forks): show workspace settings link in sidebar for fork creators (#10216)
* fix(forks): show workspace settings link in sidebar for fork creators

PR #10166 grants a fork's creator developer-management access to the
workspace settings page, but every sidebar entry point to
/workspace_settings stayed gated on is_admin || superadmin, so a
non-admin fork creator could only reach the page by typing the URL.

Widen the four sidebar guards (SettingsMenu, SidebarContent,
WorkspaceMenu, WorkspaceScopeHeader) to also admit fork owners, via a
shared isForkOwner() helper mirroring the fork-owner check already on
the settings page and the backend authorize_fork_owner_add_user grant.

Fixes WIN-2210

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

* fix(forks): check fork ownership against the settings entry's target workspace

In session mode SettingsMenu's entry targets workspaceSettingsTarget,
which can differ from the active workspace, but the fork-owner guard was
checked against the active workspace — hiding the entry when the session
targets the user's fork, or exposing a dead link when only the active
workspace is theirs. Check ownership against the workspace the entry
actually points at.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 20:39:48 +02:00
hugocasa 87be041c09 fix(git-sync): avoid percent-encoded colon in git-sync hub script path (#10213)
* fix(git-sync): avoid percent-encoded colon in git-sync hub script path

The git-sync init/detection hub script slug contained a colon stored as
`%3A` in the run-by-path URL. The generated API client re-encodes path
params with encodeURI, turning `%3A` into `%253A` (double-encoding). Some
hardened reverse proxies / WAFs reject double URL-encoding and return a
bare 400 before the request reaches Windmill, breaking git-sync repository
detection on those instances.

The hub resolves scripts by numeric id and ignores the slug, so dropping
the colon from the slug is behavior-neutral (same script, same id-keyed
worker cache) while producing a colon-free run URL.

Also force-cache GIT_SYNC_PULL_SCRIPT_PATH at build alongside
LATEST_GIT_SYNC_SCRIPT_PATH so the backend-driven pull script is always
baked into the image for airgapped workers, instead of relying on an
incidental hubPaths.json overlap.

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

* chore: bump ee-repo-ref for git-init slug match fix

Pulls in windmill-ee-private#676 so the EE is_git_init_script check matches
the colon-free git-init hub slug (GitHub App token grant for git-sync jobs).

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

* chore: bump ee-repo-ref for git-init slug helper + test

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

* chore: update ee-repo-ref to a3adea1ffb406e709cc480871df58fab6c51aca1

This commit updates the EE repository reference after PR #676 was merged in windmill-ee-private.

Previous ee-repo-ref: cef4e008ef62dec434aa9bb3ec783db8aff6a1c1

New ee-repo-ref: a3adea1ffb406e709cc480871df58fab6c51aca1

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-07-20 20:36:34 +02:00
Guilhem 0593ff7d7d feat(ai): add npm package search tool to global chat (#10204)
* feat(ai): add npm package search tool to global chat

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ai): encode npm search query before building registry URL

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(ai-evals): add global-mode npm package search case

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-07-20 18:28:09 +00:00
Guilhem c8870d36ae fix(sessions): session bar badge readouts, job persistence, refresh bounce (#10217) 2026-07-20 18:27:56 +00:00
hugocasa 2b58df57fc feat(ai): extract prompt cache token usage from OpenAI and Azure providers (#10214)
* feat(ai): extract prompt cache token usage from OpenAI and Azure providers

Parse the nested cache token details OpenAI returns and thread them into
TokenUsage, matching the Anthropic and Bedrock providers.

- sse.rs: add OpenAIPromptTokensDetails / OpenAIInputTokensDetails and the
  optional prompt_tokens_details / input_tokens_details fields.
- other.rs (Chat Completions) and openai.rs (Responses): populate
  cache_read via .with_cache(cached_tokens, None).

OpenAI's prompt_tokens/input_tokens already include cached tokens (cached is
a subset), so total/prompt are unchanged; cache_read is recorded separately
for reporting. For the same reason the frontend token-usage conversions are
left as-is (adding cached would double-count); optional cache fields and a
clarifying comment are added to prevent a future incorrect Anthropic-style fix.

Fixes WIN-2207

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

* test(ai): pin OpenAI/Azure cache-token deserialization paths

Add regression tests deserializing the real Chat Completions and Responses
usage payloads, guarding the prompt_tokens_details.cached_tokens /
input_tokens_details.cached_tokens paths against a silent rename.

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

* refactor(ai): extract to_token_usage() and test the cache mapping

Address review nit: move the usage->TokenUsage conversion into
OpenAIChatUsage::to_token_usage / OpenAIResponsesUsage::to_token_usage so
the providers call one method and the tests exercise the real mapping.
Tests now assert cache_read is populated while input/total are unchanged.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 18:47:25 +02:00
hugocasa 0e04bc6991 fix: steer ai chat away from draft-blind api catalog reads and runs (#10202)
* fix: steer ai chat away from draft-blind api catalog reads and runs

* fix: support api catalog tools in ai eval harness mock backend

* fix: block remaining draft-blind read and list endpoints in api catalog

* fix: scope eval fetch stub to handled benchmark api paths

* feat: add deployed version read option to read_workspace_item

* fix: include input schema in script workspace item reads

* feat: add pagination to list_workspace_items

* fix: paginate list_workspace_items per item type without cross-type loss

* fix: window draft overlay in list_workspace_items by page and limit

* refactor: simplify list draft overlay to capped page-1 merge

* fix: label server-synthesized draft-only rows as drafts in listings

* chore: fix stale eval comment and pin draft_only listing label
2026-07-20 18:45:14 +02:00
Ruben Fiszel 38ceae1a09 fix(db): grant schema usage and re-run windmill role grants (#10212)
* fix(db): grant schema usage and re-run windmill role grants

Migration 20250205131523 grants the windmill_user and windmill_admin
roles access to the schema and its tables, but its first statement is
LOCK TABLE pg_catalog.pg_roles, which requires superuser. On managed
Postgres (RDS, Cloud SQL) the migration user is not one, the lock
raises, and the block's EXCEPTION WHEN OTHERS handler downgrades the
failure to a NOTICE, so every GRANT after it is skipped. Core tables end
up ungranted and queries on a user_db transaction (SET LOCAL ROLE
windmill_user/windmill_admin) fail with "permission denied for table"
or, when schema USAGE was never granted, "relation does not exist".

Add a migration that re-runs those grants without the lock and without a
catch-all handler, and add the missing GRANT USAGE ON SCHEMA public to
init-db-as-superuser.sql, which PostgreSQL 15+ no longer implies for
PUBLIC. The init script also now creates windmill_admin before the table
grants so role membership is in place when they run.

Fixes WIN-2208

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

* docs(db): correct schema-privilege rationale in grant migration

PostgreSQL 15 revoked CREATE, not USAGE, from PUBLIC on the public
schema, so USAGE is still granted by default there. The explicit grant
is what a hardened database that revoked it needs, not a PG15 default.
Also correct the description of what 20250205131523's failure actually
loses: 20221105003256 grants the tables outside any locked block, so the
gap is the ALTER DEFAULT PRIVILEGES covering later-created tables.

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

* fix(db): grant only runner-owned objects to avoid aborting upgrade

GRANT ALL ON ALL TABLES IN SCHEMA raises a hard "permission denied for
table X" the moment the schema holds an object the migration runner does
not own (a superuser-installed extension such as PostGIS spatial_ref_sys,
or a co-located application table). With no catch-all handler that would
abort the whole upgrade -- a regression against 20250205131523, which
tolerated it only by swallowing every error.

Grant per-object over just the tables and sequences the runner owns,
which is exactly the set the GRANT can succeed on. Windmill's own tables
are all runner-owned, so coverage is unchanged; foreign objects are
skipped rather than aborted on. ALTER DEFAULT PRIVILEGES stays (it only
governs the runner's future objects, so it cannot conflict).

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

* fix(db): guard each grant so the migration can never abort an upgrade

Wrap every grant in its own BEGIN/EXCEPTION/WARNING so no single failure
can abort the migration -- the opposite of 20250205131523's one
block-wide WHEN OTHERS, whose flaw was granularity: a single early
failure there silently skipped every remaining grant. Here each grant
that cannot be applied is isolated, re-raised as a named WARNING, and the
rest still run.

This closes the last abort paths: a grant on an object dropped by another
session between the catalog scan and the GRANT, USAGE on a schema the
runner cannot grant, or a missing role, now warn and continue instead of
failing the upgrade. On a clean owned schema the guards never fire (zero
warnings, verified). The owner filter stays so foreign objects are
skipped without even a warning; the guard is the backstop.

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

* fix(db): also grant runner-owned views and materialized views

pg_tables returns only ordinary and partitioned tables, so the previous
loop left owned views (flow_workspace_runnables and any materialized
views) ungranted -- a coverage regression against the GRANT ... ON ALL
TABLES form, which grants views too. Those views are read through
user_db transactions, so windmill_user/windmill_admin need access.

Enumerate pg_class over the relkinds ALL TABLES covers (r, p, v, m, f),
keeping the ownership filter so foreign objects are still skipped.

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

* fix(db): grant by effective authority, not owner-name equality

Filtering relations by pg_get_userbyid(relowner) = current_user skips
objects the runner can legally grant but does not own by name: after a
migration-credential rotation, tables and sequences stay owned by the
previous runner while the new runner grants through inherited membership
(or as a superuser). Owner-name equality would leave those ungranted and
their user_db access broken.

Filter by pg_has_role(current_user, owner, 'USAGE') instead -- objects
the runner owns directly, inherits ownership of, or reaches as superuser.
Genuinely foreign objects (owner the runner is not a member of) are still
skipped, so the per-object guards remain the backstop.

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

* fix(db): scope grants by explicit membership, not superuser authority

pg_has_role treats a superuser as a member of every role, so a superuser
migration runner would grant windmill_user/windmill_admin access to every
co-located relation in the schema -- another application's tables, an
extension's tables -- not just Windmill's.

Compute the runner's role set from pg_auth_members (recursive explicit
membership) and grant only relations owned by it. This still covers the
credential-rotation case owner-name equality missed (the new runner is a
real member of the previous owner) without inheriting the superuser's
implicit authority over unrelated roles.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 18:32:47 +02:00
Guilhem f4308cf033 feat(ai): expose get_db_schema tool in global chat (#10207)
* feat(ai): expose get_db_schema tool in global chat

* fix(ai): skip cross-workspace editor cache write in global get_db_schema

* test(ai): add global eval case for get_db_schema resource lookup
2026-07-20 17:32:59 +02:00
Guilhem 542a4842a3 feat(ai): live web-search source list on chat tool cards (#10210)
* feat(ai): live web-search source list on chat tool cards

* fix(ai): restrict web-search source links to http(s), respect user collapse

* fix(ai): read plural queries field for openai web-search labels
2026-07-20 15:47:04 +02:00
Guilhem b448af1da7 feat(sessions): live DOM access for the raw-app preview in AI sessions (#10129)
* feat(sessions): live DOM access for the raw-app preview in AI sessions

Give the session chat read-only access to the rendered raw-app preview,
plus inspector-picked element chips synced bidirectionally with the preview.

- search_dom / read_dom tools: live, same-origin contentDocument reads by CSS
  selector (selector omitted = whole body), reusing the file engine over
  pretty-printed outerHTML (worker-guarded, bounded). Session-gated.
- Inspector picks become app_dom_selector context chips (selector-only); the
  model fetches content on demand. Chips shown even in GLOBAL/session mode.
- Multi-select chips synced with the preview: the chip list is the source of
  truth, pushed to the harness which renders one highlight per selector; add,
  chip-remove, and preview-× remove all stay in sync. Overlays stripped from
  search_dom output.

The ui_builder harness changes (multi-select highlights + unique nth-of-type
selectors) live in windmill-code-ui-builder and ship via the artifact re-pin.

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

* feat(sessions): inline element prompt + inspector polish for raw-app DOM

Session preview:
- Add InlineElementPrompt: a floating mini-composer anchored top-left over a
  selected element; sends a chat turn scoped to that element (its chip is
  context). Autofocus + remount per selection, borderless input, label gutter
  so it clears the harness name+size pill.
- autosize action gains an optional minHeight (default 30) so the compact
  inline input renders a tight single line.
- Fix exiting select mode: Esc / inspector toggle-off now fully clear the
  session's DOM-selector chips (source of truth) so the overlays AND the inline
  prompt are dismissed together, instead of leaving them stranded.

Full-page raw-app editor:
- Add inspectorHoverOnly: the inspector highlights on hover (outline +
  name/size) but a click selects nothing — no persistent selection, no app-mode
  context pick, no inline prompt.

Context badges:
- ContextElementBadge gains a compact prop (passed only by AIChatMessage): the
  DOM-selector chip and tagged workspace items (app/script/flow) render smaller
  above a sent message, while every badge stays the same size in the composer.

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

* fix(sessions): address Codex review P1s on raw-app DOM context

- Inline element prompt: queue the turn when the chat manager is already
  streaming (mirror the composer) instead of a concurrent sendRequest that
  would race the shared abortController / streaming buffers.
- DOM selector chips now carry the raw-app path they were picked from. Each
  preview tab renders/pushes only its own app's chips, and a tab clears
  cross-app chips when it becomes the active DOM target — a selector could
  otherwise silently resolve against whichever raw-app tab is active
  (search_dom/read_dom target the active preview).

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

* chore(raw-apps): re-pin ui_builder artifact to f79e1c3

Bumps the UI Builder artifact to the release built from ui_builder main
after windmill-code-ui-builder#18 merged (the multi-select DOM inspector
synced with the host chat). This is the artifact re-pin step that gates
merging this PR.

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

* fix(sessions): inline-prompt vertical centering + auto-collapse DOM tools

- InlineElementPrompt: bias the textarea padding (pt-[5px] pb-[3px], same
  total) so the single line sits centered in the pill — the shared autosize
  floor otherwise adds slack at the bottom and the text read 1px high.
- search_dom / read_dom: drop the explicit `autoCollapseDetails: false` so the
  tool card collapses after a successful read like other tools (it was copied
  from get_app_runtime_logs, where staying open is intentional).

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

* fix(sessions): address Codex review on raw-app DOM access

- [P1] Release the DOM-requester slot when the active raw-app tab is hidden.
  Preview tabs stay mounted, so switching to a flow/script/page (or another
  raw-app) tab left search_dom/read_dom targeting the now-hidden raw app. The
  slot is now claimed with a per-tab owner token and released via the effect
  cleanup; the runtime only lets the claiming tab clear it, so a set/release
  race between two raw-app tabs can't blank the new owner.
- [P2] Strip the inspector outline classes from the clone ROOT (the selected
  element), not just its descendants — querySelectorAll skips the root, so a
  scoped read of the selected element leaked inspector-picked in its outerHTML.
- [P2] Reword the AIChatDisplay context-chip comment to drop drafting-history
  narration ("now", "not as rows here") per AGENTS.md.

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

* fix(sessions): scope a queued inline prompt to its own element (Codex P1)

An inline element prompt submitted while a turn was streaming got queued as
plain text; if the user selected another element before the queue flushed, the
queued turn snapshotted the new selection and sent scoped to the wrong element
(or none).

The queue now carries a context snapshot:
- queueMessage(text, context?) pins the selection present at submit time.
- sendRequest accepts contextOverride and uses it verbatim as the turn's
  selected context (feeds both the optimistic bubble and the API message).
- All three queue-flush sites thread the snapshot through and restore it if the
  auto-send bails.
- With an override, only the queued message's own DOM chips are consumed from
  the live selection — a newer selection made since is left intact — preserving
  the one-shot chip semantics.

onInlinePrompt snapshots getSelectedContext() when it queues.

(Codex's other P1 — full-page-editor hover-only no longer attaching elements to
App AI — is intentional per an explicit product decision, left unchanged.)

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

* fix(raw-apps): restore full-page editor App-AI inspector context

Codex/Pi flagged a real regression: passing inspectorHoverOnly disabled the
full-page editor's existing App-AI (app-mode) inspector context. Clicking an
inspected element no longer set inspectorElement, so the app-mode
SelectedContext badge and the element attach in prepareAppUserMessage were
dead.

inspectorHoverOnly and the session path (onInspectorSelect) are mutually
exclusive — the flag was only ever set in the full-page/app-mode context, so it
only suppressed the app-mode path. Remove it entirely (prop, guard, and the
pass in +page.svelte), restoring the original click -> inspectorElement ->
app-mode context behavior. Session behavior is unchanged; the hover highlight
still works in both.

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

* fix(sessions): snapshot inline-prompt context synchronously (Codex P1)

The immediate inline-prompt path called sendRequest without a context override,
so the selected element was read only after the async send preflight
(attached-file refresh, beforeSend, global-skill refresh). Picking another
element during that window attached the wrong one to the outgoing turn.

onInlinePrompt now snapshots the selection synchronously at submit time and
passes it as contextOverride on both the queued and immediate paths, so the
prompt always rides with the element it was scoped to.

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

* fix(sessions): drop a queued inline prompt's DOM chips when the app changes

A queued inline prompt scoped to raw app A described A's elements, but the
runtime's single DOM requester targets whichever preview is active. Switching
to app B before the queue flushed left the turn describing A while search_dom /
read_dom would query B.

When a raw-app preview tab becomes active it now also strips the queued turn's
DOM chips belonging to other apps (dropQueuedDomContextForOtherApps), so a
queued prompt can't ask the model to read one app's selectors against another's
live DOM. Non-DOM queued context is preserved.

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

* fix(sessions): scope inline prompt to its anchored element (Codex P1)

The inline prompt sits over a single element but snapshotted EVERY selected DOM
chip. With several elements selected (Shift-multi-select), a prompt shown over
B sent both A and B as context, so "change this button" couldn't identify which
one it meant.

onInlinePrompt now uses its anchored `selector` argument (previously ignored)
to keep only that element's DOM chip in the snapshot; non-DOM context is
preserved.

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

* fix(sessions): preserve original context on edit/retry (Codex P1)

restartGeneration resent with the live selection, so editing or retrying an
element-scoped prompt lost its DOM selector (or adopted a newer one) — DOM
chips are one-shot and cleared from the live selection after the first send.

It now passes userMessage.contextElements as contextOverride, re-using the
exact context the message was originally sent with. undefined (modes that don't
attach contextElements) falls back to the live selection as before.

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

* feat(sessions): route search_dom/read_dom to the selector's own app preview

The DOM requester was a single active-tab slot, so search_dom/read_dom always
read whichever preview was visible. A turn scoped to app A that read the DOM
after the user switched to app B would silently read B (Codex P1).

Preview tabs stay MOUNTED when hidden, so app A's DOM is still there — the fix
is to route by app instead of "active tab":

- Every mounted RawAppEditorView registers its DOM requester keyed by its app
  path (runtime holds a map, not one slot); the visible tab is tracked
  separately as the default target.
- search_dom / read_dom gain an `app_path` (surfaced per chip in the SELECTED
  DOM ELEMENTS block and the tool schema); the runtime routes the query to that
  app's still-mounted preview. No app_path → the active preview, or the only one
  open.
- If the named app's preview has been closed, the tool returns "The preview for
  X is no longer open…" so the chat can explain it, rather than reading the
  wrong app.

This makes the queued-context drop-on-switch (dropQueuedDomContextForOtherApps)
redundant — removed. The lightweight submit-time snapshot (which element the
message is about) is kept.

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

* fix(sessions): address Codex review on DOM access (script strip + app-scoped chips)

- search_dom/read_dom: strip the app's compiled <script> bundle from the
  whole-body clone so queries only see rendered HTML, not source (P1).
- DOM chip add-dedup and removal now match both selector AND app path, so an
  identical selector in another app can't drop or block a chip (P2).
- Refresh stale single-slot requester comments to describe per-app routing;
  reword the foreign-chip reset to reflect why it stays (composer chips are
  unlabeled by app) (P2).

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

* fix(sessions): scope DOM-chip clear to its app so a rebuild can't wipe another (Codex P2)

Every mounted preview emits inspectorClear on rebuild, routed to
onInspectorClearAll → clearSelectedDomElements(). Unscoped, a hidden app A
rebuilding cleared app B's active chip and highlight. clearSelectedDomElements
now takes an optional appPath; the preview-clear path passes its own path, while
post-send and foreign-reset clears stay unscoped.

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

* fix(sessions): reject a <script>-root DOM query so the app bundle can't leak (Codex P2)

The descendant strip (querySelectorAll('script')) skips the clone root, so a
search_dom / read_dom query whose selector targets `script` serialized the whole
compiled bundle. Reject a script root with an explanatory result before cloning.

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

* fix(sessions): union queued inline-prompt DOM context + reject inspector-label root (Codex)

- Multiple inline prompts queued during one stream accumulate their text, but
  queuedContext replaced (last wins) — dropping an earlier element's chip and
  misapplying its instruction. Union the DOM selector chips across queued prompts
  (non-DOM context still from the latest snapshot) (P1).
- search_dom/read_dom: reject an .inspector-label clone root, like the <script>
  root — the descendant strip skips the root, so a `.inspector-label` query
  would return inspector chrome (P2).

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

* fix(sessions): restore consumed DOM chips when a turn is cancelled unsent (Codex P1)

DOM selector chips are consumed from the live selection before the request goes
out. If the user hits Stop before any usable output, restoreUnsentTurn put the
text/pastes/images back but not the chips, so resending the restored prompt lost
its element scope. Re-add the consumed chips on rollback (skipped on a queued-
message handoff, which carries its own context).

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

* fix(sessions): edit box edits the message's own context, sends what it shows (Codex P1)

Editing a past message bound the edit box to the LIVE selection while the resend
carried the message's original contextElements — so the box showed one set of
chips (or none) but sent another, and add/remove in the box did nothing. The edit
box now edits a copy seeded from the message's own contextElements, and
restartGeneration sends that edited copy; a bare retry still falls back to the
original. Drops the now-unused selectedContext prop from AIChatMessage.

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

* fix(sessions): keep DOM context coherent on dequeue/cancel + per-chip edit identity (Codex)

- dequeueMessage dropped the queued draft's pinned DOM context, so returning it
  to the composer sent the live selection instead. Restore the draft's context on
  dequeue (P1).
- Cancel-rollback re-added this turn's chips additively, mixing in chips selected
  mid-stream. Replace instead so the restored draft stays coherent — shared helper
  #restoreDomContext used by both paths (P1).
- The context chip row keyed/removed by (type, title); repeated DOM elements share
  a title (two button.btn), so editing a multi-select message gave duplicate keys
  and deleting one chip removed both. Identify DOM chips by (appPath, selector) (P1).

The queued-prompt instruction↔element association (Codex #1) is the accepted union
tradeoff and is unchanged.

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

* fix(sessions): scope read_dom pagination + don't consume live chips on edit/retry

Codex P1s:
- read_dom's continuation note only renamed read_file, so following it with
  start_line alone dropped app_path/selector and re-read the active app's whole
  body instead of continuing inside the element.
- contextOverride also carries an edit/retry's replayed copy of an older
  message's context. Consuming it from the live ContextManager stripped an
  identical chip the user had since selected in the composer, leaving their
  draft unscoped. Overrides now declare their origin ('pinned' vs 'replay').

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

* fix(sessions): keep DOM context paired with the draft it belongs to

Restoring a draft's pinned DOM chips replaced the live selection unconditionally,
but both restore paths can leave a different draft in the composer:

- Cancel: an occupied composer declines the rolled-back text (restoreInstructions
  bails), yet the cancelled turn's chips still replaced the live ones — the draft
  the user typed during the stream kept its text but got retargeted.
- Dequeue: queued text is prepended onto an existing draft, so both instructions
  share one composer; replacing the chips dropped the standing draft's element.

restoreInstructions/prependText now report whether the composer took the text.
Cancel restores context only when it did; dequeue keeps both drafts' chips when
it merged into one.

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

* fix(sessions): scope an inline prompt's chip filter to its own app

The filter narrowing a multi-select down to the anchored element matched on
selector alone. Selectors are generated per app and collide across them, so an
inline prompt in app B also carried app A's identically-named chip, leaving the
model two indistinguishable referents. Since draft restoration can now
legitimately hold chips from several apps, match (selector, appPath) — as the
sibling deselect/clear handlers in this file already do.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:33:45 +02:00
Ruben Fiszel 4f1d636005 fall back to createFlow when the update target does not exist (#10206)
FlowBuilder's deploy branches solely on the `newFlow` prop. When an embedder
mounts the builder with `newFlow` false for a flow that was never deployed,
Deploy issues `PUT /flows/update/{initialPath}` and the backend answers
`Flow not found at name <path>`, so the first deploy can never land.

Confirm against `GET /flows/exists/{path}` before taking the update branch,
and create instead when nothing is deployed there. The create path and the
full-page editor (where `newFlow` is derived from `no_deployed`) are
unchanged.

Fixes WIN-2206

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 13:10:26 +02:00
Ruben Fiszel 71f2d47cb4 feat: cap queued jobs per concurrency key on cloud (#10197)
* feat: cap queued jobs per concurrency key on cloud

* fix: close preprocessed-flow bypass and bound concurrency cap scan

* fix: only cap concurrency keys with an active concurrent_limit

* chore: only load concurrency key cap setting when cloud hosted

* fix: reject queued-job import on cloud
2026-07-20 12:33:40 +02:00
Ruben Fiszel 1abfe49f7e stop the app editor crashing on a grid-less draft (#10203)
* fix(apps): default a missing grid so the app preview does not crash

* fix(apps): migrate the adopted draft so the editor matches the viewer

* docs: tighten grid-normalization comments to durable constraints

* test: drop the migrateApp grid-default test
2026-07-20 12:33:14 +02:00
hugocasa 83a354f831 feat: expose windmill api endpoint catalog to global ai chat (#10199)
* feat: expose windmill api endpoint catalog to global ai chat

* fix: guard variable reads and deletes in ai chat api catalog

* fix: clarify api catalog prompt example for run result access

* fix: block deleteScriptByHash and document resource read boundary
2026-07-20 11:31:51 +02:00
Ruben Fiszel b5e69ffba6 fix(security): enforce token scope filtering on folder list endpoints (#10193)
* fix(security): enforce token scope filtering on folder list endpoints

* fix: apply folder scope filtering before pagination

* fix: keep SQL pagination for unrestricted tokens on folder lists

* fix: bound scoped folder-list scan with chunked pagination

* fix: use keyset pagination for scoped folder-list scan

* refactor: reduce folder scope fix to the minimal predicate filter

* chore: drop folder list scope test
2026-07-20 07:50:50 +02:00
Ruben Fiszel 70359e3a76 fix(schedules): apply scope-path filtering to schedule list endpoints (#10192)
`list_schedule` and `list_schedule_with_jobs` returned every schedule in
the workspace regardless of the token's declared scope. A token limited
to `schedules:read:<prefix>/*` could enumerate all schedule paths (and
their script paths, cron expressions and recent job outcomes) through
these two endpoints, while `get_schedule` correctly rejected them.

Apply `build_scope_path_predicate(&authed, "schedules", "read")` to the
returned rows, mirroring scripts, flows, apps, resources and variables.
In `list_schedule` the filter runs after the draft-only append so both
deployed and draft rows are covered.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 07:32:52 +02:00
Ruben Fiszel f32d7702bc fix(inputs): add ownership check to saved-input args read endpoint (#10194)
`get_args_from_history_or_saved_input` looked up saved-input rows by
`id + workspace_id` only, without checking ownership or `is_public`. The
`input` table has no RLS policy, so any authenticated workspace member
could read another member's private saved-input args (which may hold
credentials) just by knowing the UUID, even though `inputs/list` already
scopes to `is_public OR created_by = caller`.

Add `AND (is_public IS true OR created_by = $4)` to both `input`-table
queries in the handler (the `?input=true` single-query branch and the
default `v2_job UNION ALL input` branch), matching the predicate already
used by `list_saved_inputs`.

The `v2_job` branches are already covered by the table's RLS policies
enforced via `SET LOCAL ROLE windmill_user` in `UserDB::begin()`, so they
are left unchanged.

Fixes WIN-2202

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 07:31:02 +02:00
Ruben Fiszel 60a7a23a04 chore(main): release 1.763.0 (#10186)
* chore(main): release 1.763.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.763.0
2026-07-19 10:45:40 +02:00
Ruben Fiszel 89948bb441 chore(git-sync): bump hub scripts to windmill-cli@1.762.3 (#10189) 2026-07-19 10:40:29 +02:00
Ruben Fiszel 2d77e74207 fix(cli): stop emitting has_on_behalf_of/has_permissioned_as: false on pull (#10188)
With syncBehavior v1 the pull strips the user-specific on_behalf_of_email /
permissioned_as from metadata and keeps a boolean marker so a later push can
preserve remote ownership. The marker was written unconditionally as
`!!<field>`, so every ownerless script, flow, schedule and trigger (the vast
majority) got a `has_on_behalf_of: false` / `has_permissioned_as: false` line,
producing a spurious diff on every pull.

Absence of the marker already means "no owner" everywhere it's read on push, so
only emit the key when true.

Fixes WIN-2201

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:30:49 +02:00
Ruben Fiszel 4bddefc518 wire topBar.path and editablePath customUi in FlowBuilder (#10187)
Mirror the ScriptBuilder whitelabel pattern in FlowBuilder so the
`customUi.topBar.path` and `customUi.topBar.editablePath` options actually
take effect:

- add `editablePath?: boolean` to `FlowBuilderWhitelabelCustomUi.topBar`
- gate the EditorHeader block on `customUi?.topBar?.path != false` and pass
  `pathEditable`/`summaryEditable` (the latter wires the pre-existing but
  unused `editableSummary` flag)
- hide the Path field in FlowSettings when `topBar.editablePath` is false

Defaults are unchanged (`!= false`), so non-whitelabel flows still show the
path header and settings field.

Fixes WIN-2200

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:28:30 +02:00
Guilhem 42da20ae97 feat(ai): gate data pipelines in sessions behind a dev flag (#10178)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:13:27 +02:00
Ruben Fiszel d5b0a1b5c7 chore(main): release 1.762.2 (#10184)
* chore(main): release 1.762.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.762.2
2026-07-18 08:49:56 +02:00
Ruben Fiszel f6e36f862e chore(schedules): lower reconciler re-arm back-off cap to 8 passes (#10182)
Follow-up to #10179. The exponential back-off between reconciler re-arm
retries of a persistently-failing schedule capped at 32 passes (~2.7h at
the default 5-min reconcile cadence). Lower the cap to 8 (~40min) so a
schedule fixed out of band (a lapsed license renewed, a bad cron corrected
directly in the DB) auto-recovers within a few passes, while still cutting
the retry rate sharply versus retrying every pass. Fixes via the UI/API
re-arm immediately and are unaffected.

Fixes WIN-2198

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 08:49:41 +02:00
Ruben Fiszel cba5f0d8a8 fix(scripts): populate auto_kind from draft JSON for draft-only scripts (#10183)
The scripts list endpoint (include_draft_only=true) only populated
auto_kind for the pipeline case, leaving library draft-only scripts
(no `main` function) with auto_kind: null even though the frontend
saves auto_kind: "lib" into the draft JSON. This made it impossible
to distinguish library draft-only scripts from regular ones without a
separate per-script API call.

Fall back to the auto_kind saved in the draft value after the
content-derived pipeline check, which keeps priority since it mirrors
the deploy-time computation.

Fixes WIN-2199

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 08:39:22 +02:00
Ruben Fiszel 252ffd0401 chore(main): release 1.762.1 (#10181)
* chore(main): release 1.762.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.762.1
2026-07-17 22:52:46 +02:00
Ruben Fiszel c82056cfde fix(schedules): stop disabling schedules on transient push errors (#10179)
* fix(schedules): stop disabling schedules on transient push errors

A scheduled flow whose next-occurrence push failed after retry exhaustion
used to be disabled, killing a healthy schedule over a transient DB blip
(pool contention, statement timeout). Now that the unarmed-schedule
reconciler exists (#10174), transient failures no longer disable: the
current occurrence runs to completion and the reconciler re-arms the next
occurrence once this run leaves the queue.

In the flow schedule-push path after retry exhaustion we now branch on the
error: QuotaExceeded/NotFound still disable (the schedule's own fault, and
rearm_schedule would otherwise leave them enabled-yet-unarmed forever),
while transient errors are only reported and the flow continues.

The previous iteration returned a SchedulePushZombieError to force a zombie
restart; that is removed, because zombie detection cancels (does not
restart) same-worker flows, so it would have lost the current run of a
same-worker scheduled flow. The now-obsolete SchedulePushZombieError type
and its catch in worker.rs are deleted.

Fixes WIN-2198

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

* fix(schedules): back off and surface repeated reconciler re-arm failures

The unarmed-schedule reconciler retried a schedule that could not be
re-armed on every pass, forever, logging only to the server. With the flow
schedule-push path no longer disabling on non-transient errors, a
persistently-broken push (bad stored cron/timezone/args, lapsed license
key) now stays enabled and would spin in that loop silently.

The reconciler now tracks consecutive re-arm failures per schedule:
exponential back-off (2, 4, 8, … passes, capped) between retries so a
broken schedule is not hammered, and after 3 consecutive failures it
surfaces the cause once (records schedule.error + raises a critical alert)
without disabling. Both reset the moment the schedule re-arms, which also
clears the recorded error.

Verified end-to-end on a running server: a flow schedule with a corrupted
cron stays enabled, retries back off, the error is surfaced after the
third failure, and it re-arms and clears the error once the cron is fixed.

Fixes WIN-2198

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 22:48:15 +02:00
Ruben Fiszel 203f6c69dd fix(worker): gate ansible socket-dir name check to unix (#10180)
is_persistent_control_path_dir_name is only referenced from
prepare_socket_root, which is #[cfg(unix)]. On Windows nothing used it,
so -D dead-code failed the build. Gate the function and its unit test to
unix, matching the reaping code they support.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 21:26:40 +02:00
Ruben Fiszel 3ffce7e2cc chore(main): release 1.762.0 (#10175)
* chore(main): release 1.762.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
v1.762.0
2026-07-17 20:15:13 +02:00