A settings save reported the fork pointers left resolving to nothing only
for the names in `deleted_datatables`, which `wmill sync push` never sends.
The save now works out what it removed from the locked entries, and the
CLI prints the stranded pointers it returns.
Also correct the replication helper's contract: no role or admin check
makes a replication connection safe, so a data table under roles is
refused outright rather than gated as an admin operation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
* feat: let a native trigger be disabled without deleting it
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: show and control the native trigger pause outside the flow editor
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: create a native trigger already paused instead of pausing it after
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: correct the native trigger enabled comments for create-time init
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Putting it inside `custom_instance_pg_databases` was the wrong call, and it cost two ways.
The catalog serializes a generated Postgres password per role, and that row is the
operator-facing instance config, so the passwords reached `get_instance_config` and its YAML
editor — a live cluster credential in a response body, a UI field and any log of either.
Worse in the other direction: `to_settings_map` strips the catalog, so a full-row upsert of
that key writes the row back without it and the catalog is gone, while the cluster keeps every
login it described.
`custom_instance_replication_pwd` is the precedent and says exactly why — a generated secret,
written only by the server, never operator-authored, hidden so the config machinery cannot
read, rewrite or drop it. The catalog is the same thing, so it now has the same shape:
`datatable_roles`, in `HIDDEN_SETTINGS`, `PROTECTED_SETTINGS` and the agent-worker denylist.
No redaction to keep in step with three code paths, and no way for a neighbouring write to
take it out.
Two races on the same shared documents. `edit_datatable_config` read the stored data tables
outside its transaction and then wrote the whole `datatable` document, so a permissions save
committing in between was silently rolled back; it now reads under `FOR UPDATE`. And
`set_datatable_permissions` validated role ids against the catalog before opening its
transaction, so a deletion in between let it write a deleted role back — including as the
default, which every later job then fails on; it now holds the catalog lock and the settings
row across validation and write.
Completes the authorization contracts the previous commit claimed but did not finish:
`read_datatable_entry` (which it named and missed), `resolve_governing_datatable`, whose whole
job is to answer for a workspace the caller may not belong to, and
`converge_connect_grants_with`, which had not inherited its wrapper's.
Also the generic Python SDK reference: `_format_py_params` learned the bare `*` last time, but
`extract_py_functions` is a second formatter and still rendered `datatable(name, role)`, so
code written from that page passed a keyword-only argument positionally.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* feat: recognize `// volume:` mounts in PHP scripts
Volume annotations were parsed for every language but PHP, so a PHP script
could not mount a workspace volume. Two things stood in the way: PHP had no
entry in the comment-prefix maps, and a PHP script opens with `<?php`, which
ends the leading comment block the parsers scan before any annotation is read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3FR7iS9nRhpFt615cnuQ7
* fix: tolerate a PHP opener that carries code, drop the inert CLI hunk
The open-tag skip matched `<?php` exactly, so `<?php declare(strict_types=1);`
still ended the leading comment block and every annotation below it was silently
ignored. Match the tag as a case-insensitive prefix and skip the whole line.
The CLI local-graph hunk could never fire: PHP has no wasm asset parser, so
`fallbackParse` handles it, and its own header scan stops at `<?php` — the script
is dropped as a non-pipeline-member before any volume asset is read. Making only
the CLI PHP-aware would also put the local graph out of parity with the deployed
one, whose `parse_pipeline_annotations` stops there too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3FR7iS9nRhpFt615cnuQ7
* docs: correct the CLI mirror comment, state the own-line annotation rule
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3FR7iS9nRhpFt615cnuQ7
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
A data table backed by the instance database resolved to exactly one Postgres connection,
`custom_instance_user`, for everyone who could reach it at all. There was no way to say
this job reads, that one writes, this one never sees the salaries table.
A data table role is now a real Postgres login on the cluster, defined once for the
instance by a superadmin and named exactly as they named it. A script that declares
`-- role analytics` connects as `analytics`, and Postgres decides what it may touch —
grants are ordinary SQL. Windmill answers only "may this caller ask for this role", from
the tenant lists on the data table entry: `u/alice`, `g/analysts`, `f/finance` or `*`.
A data table with no `permissions` block behaves exactly as before.
Everything that opens a connection on someone's behalf goes through one chokepoint,
`get_datatable_resource_from_db`, which takes the identity explicitly and fails closed when
there is none. The role logs in as itself — never `SET ROLE`, which a script could
`RESET ROLE` its way out of.
A fork's data table entry becomes a pointer at the workspace that governs it rather than a
copy of it. The settings clone used to hand a fork a byte-identical entry naming the
parent's database, which a fork admin could edit to grant themselves `admin` there; a
pointer has nothing local to edit, and its tenants are evaluated as a member of the
governing workspace, by email. `permissions` is stripped from the workspace export and
ignored on import: tenants name principals of one workspace, and a settings push is not
where an access decision should be made.
Operations that see the whole database whatever the roles grant stay with the governing
workspace's admins: editing the roles, a migration that declares none, and opening a
replication stream for a Postgres trigger or capture.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(cli): keep permissioned_as on single-item push, as sync push does
* fix(cli): resolve syncBehavior from the target workspace, not the branch alone
* refactor(cli): share the workspace-name resolution between sync and single-item push
* test(cli): import the moved workspace-name helper from its new home
* docs: teach agents to pass a resource as $res:<path> in run arguments
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XjRARL7JA7xm772iJP4mJk
* docs: extend run-argument rule to in-editor chats, fix run-as wording
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XjRARL7JA7xm772iJP4mJk
* docs: tighten resource run-argument rule after review
- Drop the false rationale that "$var:" only works inside a resource value
from the write_variable description and its runtime rejection message; keep
the rule (a variable cannot reference itself).
- MCP resource-argument description: the title fallback renders "No title",
so say the title is only a label rather than that it can be empty. Guard the
real-newline fix with asserts in the existing enrichment test.
- Eval: assert the full "$res:f/evals/global/github_main" value as one prefix
so a wrong path with a right prefix fails.
- resources.md: narrow "a trigger's payload" to its configured static args.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XjRARL7JA7xm772iJP4mJk
* docs: scope the run-argument rule to global chat, add an exact eval matcher
The ai_evals A/B on the two in-editor modes showed no effect: script mode
sonnet 5/5 both with and without the description, flow mode sonnet 5/5 and
haiku 5/5 on the baseline alone. A flow's input schema already carries
`format: resource-<type>`, so those modes have a signal global mode does not
give. Revert both files to keep the tool schemas free of a description that
buys nothing per iteration; global mode keeps it, where haiku goes 0/5 -> 5/5.
Add `stringEqualsAnyOf` to toolCallArgs and use it for the resource reference:
nothing in the eval resolves the value, so a prefix match accepted a near-miss
path like `$res:f/evals/global/github_main_backup`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XjRARL7JA7xm772iJP4mJk
* docs: address cubic review — CLI wording, mock resource getter
- `-d --data` help on all four run/preview commands: give $res: and $var:
their own clauses instead of a parenthetical that read as if a resource
were a kind of variable.
- Mock backend: `getBenchmarkResource` now resolves AI-provider seeds as well
as plain ones, so it agrees with `existsResource` and `listResource` — both
report either kind, and a case that listed a resource and then read it by
path got a row it could not fetch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XjRARL7JA7xm772iJP4mJk
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: connect to dev server instead of localhost
* fix: derive WebSocket scheme from location.protocol
Mirror the protocol-aware pattern used by initSqlWebSocket in dev.ts
so the WebSocket connects over wss:// when the dev server is reached
through an HTTPS proxy/tunnel, avoiding mixed-content blocking.
* refactor: drop now-unused port parameter of wmillTsDev
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HsfdN82yP88qyQ3h8Lwv2v
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(ai-chat): make reusable skills ai_skill resources you select per workspace
* chore: pin the ee ref to the skill telemetry counters
* fix: address review findings on skill authoring, import and migration
* fix: enforce skill selection in read_skill and stop imports clobbering resources
* feat: carry format_extension from the hub into synced resource types
* fix: let an edit set or clear a resource type's format_extension
* fix: regenerate the sqlx cache and close the review round findings
* fix: close the round-2 findings on folder ACLs, cached sync and truncation
* refactor: make the skills migration non-destructive and use design-system inputs
* fix: close the round-4 findings on folder owners, startup sync and truncation
* fix: clear obsolete extensions, guard folder owners, and report skipped skills
* fix: honor explicit-null extensions and report same-type migration conflicts
* fix: scope skill actions to the committed workspace and paginate the listing
* fix: keep the drawer scoped to the live workspace and surface truncation
* fix: discard a skills refresh for a workspace the chat has left
* chore: update ee-repo-ref to 6efe7a73c745c2e1377a34498523c00d89010a3d
This commit updates the EE repository reference after PR #764 was merged in windmill-ee-private.
Previous ee-repo-ref: 55998c142bc72edd08532748af1974b16035658d
New ee-repo-ref: 6efe7a73c745c2e1377a34498523c00d89010a3d
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: keep a local dbt descriptor under sync pull --keep-deleted
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174o6mGTWoanipUgf5zNcVL
* fix: keep an added-shaped dbt descriptor removal under --keep-deleted too
A stateful pull compares `.wmill`, not the working tree, so a descriptor
missing from that map still arrives as `added` while a real file with the
project's warehouse and run arguments sits on disk. Counting only `edited`
left that file deletable, and silently: the flag logged nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174o6mGTWoanipUgf5zNcVL
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: strip the script/ prefix from trigger error handler paths
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: strip the script/ prefix when collecting trigger handler refs
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: relocate prefixed trigger error handlers on project retarget
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: reject a prefixed error_handler_path on triggers instead of resolving it
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: describe error_handler_path as a bare script path in the api schema
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(cli): keep svelte component styles in the raw-app bundle
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test: fold svelte style guard into the plugin test file
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: record the editor-parity constraint on the svelte css option
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(cli): pin esbuild's service cwd before any test file chdirs
esbuild's node API captures process.cwd() when its module is first
imported and spawns its service with that cwd on every (re)start.
createBundle stops the service after each bundle, so the cwd is reused
across the whole run.
Several test files chdir into a temp dir and delete it afterwards. The
first one to bundle therefore pinned the service to a directory that
stopped existing, and the next test to reach esbuild died with
The service was stopped: ENOENT: no such file or directory,
posix_spawn '.../@esbuild/linux-x64/bin/esbuild'
The binary is present; ENOENT is posix_spawn rejecting the missing cwd.
Which file tripped it depended on bun's readdir order, so renaming an
unrelated test file was enough to surface it. Importing esbuild from the
preload pins the service to a cwd that outlives the run, independent of
file ordering.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G88YF3sZFnJZUvTLVjqhZc
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* fix: keep raw-app files within their app folder on sync pull
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: validate raw-app file keys as stored, closing nul and duplicate-field bypasses
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: guard raw-app runnable ids too and fail closed on unparseable value
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: strip only a leading slash on raw-app file keys to match backend
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: strip only a leading slash on raw-app file keys to match backend
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* test: assert the unpacked repo symlink without following it
`unpack_keeps_a_link_that_stays_in_the_repo` read through the link it had just
unpacked. Windows stores a symlink's target verbatim and its object manager
rejects the `/` in a POSIX one, so `read_to_string` came back with
`ERROR_INVALID_NAME` and the release's `cargo_test_windows` job was red.
Pin what the function is responsible for on every platform — the link is kept
and materialized — and read through it only where a POSIX relative target
resolves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3WRtxdKNGdomWX9vaAYGx
* test: key the cli sync-map fixtures with the platform separator
A sync map is keyed with the platform separator on both sides — `FSFSElement`
walks the tree with `path.join`, and the remote `ZipFSElement` starts at
`"." + SEP` and joins from there — while an `!inline` reference is always
forward-slash. `lock_dedup.ts` follows that convention; the fixtures did not,
so on Windows they built a map shape the CLI never produces and 12 of them
failed. `getTypeStrFromPath` is the same story: it matches
`"dependencies" + SEP`, and the test handed it a forward-slashed path.
Build the fixture keys through the separator, leaving the `!inline` references
and the `present` map forward-slash, as `sync.ts` hands them over.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3WRtxdKNGdomWX9vaAYGx
* ci: skip the discord comment relay when the thread lookup returns none
A rate-limited or unauthorized Discord response carries no thread list, and
under `bash -e` that aborted the step — jq cannot iterate null, nor parse the
HTML error page Cloudflare answers a 429 with — before it reached the "thread
not found, skipping" branch right below. Three comment relays failed that way
on the 1.794.0 head.
Keep the step green for both, but tell them apart: a response with no thread
list is a delivery that was dropped for a reason worth seeing, so it warns with
the body it got, while a PR that genuinely has no thread stays quiet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3WRtxdKNGdomWX9vaAYGx
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>