change_workspace_id copies workspace_settings by an explicit column list that
omitted guest_jwt_public_key and guest_jwt_jwks_url, so a rename silently cleared
the guest JWT key, the same failure the parent fixed for guest_access_enabled.
Add both columns to the copy, and extend the rename test to assert the key
travels. Sync verify()'s refusal message with the other two sites (leading `/`).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
parse_jwks_keys filtered on metadata only (kty/alg/use/key_ops), but jsonwebtoken
carries n/e/x/y as strings and defers decoding to auth time, so a JWKS whose only
key had malformed material passed save-time validation and every token failed
later. Keep a key only if DecodingKey::from_jwk decodes it. This is the single
source for both edit_guest_jwt_key and per-request verify.
Update two test comments that credited the SPKI parse alone now that the guard
also accepts a PKCS#1 RSA public key.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
The SPKI guard decoded the body with the strict RFC 7468 decoder, which enforces
64-column wrapping, so a legitimate public key wrapped otherwise (or a PKCS#1
`RSA PUBLIC KEY`) was refused where jsonwebtoken would have parsed it. Decode the
body leniently like jsonwebtoken, then require the DER to be a public-key
structure: an SPKI (RSA or EC) or a PKCS#1 RSA public key. Private material
satisfies neither, so the round-13 bypass stays closed. Test adds a one-line
(non-64-column) public key as a positive control.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
jsonwebtoken 8.3 decides public vs private from the PEM label alone and never
inspects the DER, so private material relabelled `PUBLIC KEY` (or a PKCS#1 key
relabelled `RSA PUBLIC KEY`) passed the earlier label check and would be stored,
then served back through the settings response. decoding_key_from_pem now parses
the DER as a SubjectPublicKeyInfo, which only public keys satisfy, before it is
persisted or used. The regression test relabels complete, valid private keys
(EC PKCS#8, RSA PKCS#1) so the guard is what refuses them, not malformed DER.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
jsonwebtoken 8.3's from_rsa_pem accepts private encodings (PKCS#1, PKCS#8), so
a pasted private key would be stored and then served back through the settings
response. Refuse any private PEM in decoding_key_from_pem, the single choke
point for both the save endpoint and per-request verification.
Also drop the last "canonical" references the is_scope_literal_path switch left
in the JWT arm: the refusal log no longer misdiagnoses a reserved character as a
malformed path, and the relocated guest_session_scopes doc carries the
sentinel/label distinction for both its callers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
Adopt the parent's app-path guard for scopes: windmill_common::auth::
is_scope_literal_path (refuses only `:`, `,`, `*` and a leading `/`) rather than
check_proper_path, since app paths may carry spaces and `@`. The relocated
guest_session_scopes and the JWT arm's verify() both use it. The parent's
re-added local guest_session_scopes in users.rs stays relocated to
windmill_api_auth::scopes, its guard preserved there.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
Save the guest JWT key before the Enterprise-only default-app and rate-limit
writes, so a refused write cannot swallow a valid key change on CE. Name the
JWT entry in the Guests card summary. Complete verify()'s doc with the email
and app_path rules. Anchor the refusal suite with a positive control and make
enable_guests assert its status, so a broken fixture cannot pass it vacuously.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
jsonwebtoken 8.3 models a JWK's `alg` as its signing `Algorithm` enum, so a set
carrying an encryption key (`alg: "RSA-OAEP"`, absent from that enum) alongside a
signing key failed whole-set deserialization and the whole JWKS was rejected,
which real issuers publish. Parse each key on its own and skip one that does not
model as a JWT key, keeping the usable signing keys. Unit-tested with a mixed set.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
- Gate the audit with a conditional upsert (`DO UPDATE ... WHERE NOT
guest_activity.jwt_entry RETURNING 1`) read with fetch_optional. The row comes
back exactly once per email per day, decided by the conflicting tuple, so it
keeps the atomicity `xmax = 0` had (no double audit when two first requests race
on a metered instance, which takes no advisory lock) and still fires on the
first JWT after an IdP sign-in created today's row. The prior CTE decided this
from the statement snapshot and could double-audit.
- Negative-cache only a real allowance refusal (`PermissionDenied`); a transient
DB error inside guest_admission denies this request but no longer locks the
email out for 30 seconds.
- Refuse a JWKS key whose `key_ops` is present and omits `verify`: it is published
for something other than signature verification. Unit-tested.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
- Gate the JWT `users.login_guest` audit on the transition to `jwt_entry = true`,
not physical insertion: a guest who signed in through the IdP earlier the same
day already has a `guest_activity` row with `jwt_entry = false`, and the old
`xmax = 0` test suppressed the first JWT audit. A CTE reads the prior flag and
the RETURNING decides it atomically in the upsert.
- Fold the signed-in mint's inline account check into `has_any_account`, so the
helper has both callers and the two no longer diverge on lowercasing.
- Negative-cache a refused guest JWT for 30s so a replayed bearer past the cap
does not take the instance-wide allowance advisory lock on every request.
- Update the two stale share-link header comments to the `guest.<jwt>` form,
drop the "plan gate" rationale on the entry test's cfg, collapse the blank
lines the SHARE_LINK_SEGMENT removal left, and prettier-format the settings
card after the isEnterprisePlan wrapper was removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
- admit_and_record_guest_jwt commits the guest_activity row before the
best-effort audit, on a separate connection. The EE audit writer swallows an
audit_partitioned failure but the failing statement still aborts its
transaction, so auditing before the commit would roll the activity row back
while the arm returned success, admitting a guest uncounted and past the
allowance.
- The share-link guest marker is now the prefix `guest.` glued to the token
(`/a/<path>/guest.<jwt>`, `/public/<ws>/<secret>/guest.<jwt>`). The `.` cannot
appear in a custom-path or secret segment, so an external-JWT link whose custom
path ends in a `guest` segment (`/a/foo/guest/<jwt>`) is read as before rather
than hijacked. Removed the unused SHARE_LINK_SEGMENT constant.
- Moved the JWT allowance test to its own binary (app_guest_jwt_allowance.rs):
set_plan flips a process-global license key, so a test sharing the binary with
the existing allowance test would race under --test-threads.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
* feat: add sso_groups_claim setting for login-time instance group sync
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YESxWqzt959S6TY6vbc4eG
* chore: bump ee-repo-ref for the SSO groups claim reconcile
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YESxWqzt959S6TY6vbc4eG
* chore: update ee-repo-ref to 3b89bfc11314a326a191101cfe3ef65f6f7f82a8
This commit updates the EE repository reference after PR #774 was merged in windmill-ee-private.
Previous ee-repo-ref: e388527f9adbbe466fe050ca8d1d236ce3342bc3
New ee-repo-ref: 3b89bfc11314a326a191101cfe3ef65f6f7f82a8
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
- Validate the email claim with `windmill_common::users::VALID_EMAIL` (the `usr`
table's own constraint) plus the 254-byte bound, rather than a hand-rolled
predicate. It requires an `@`, which is what keeps a guest's name its own
principal, never a `u/<user>` or `g/<group>`.
- Reject an `app_path` claim carrying a scope metacharacter (`*`, `,`, `:`,
whitespace) before authenticating: it is interpolated into `apps:read:<path>`
and `apps:run:<path>`, where `*` or `,` would widen the guest past its one app.
- `isJwt` on the custom-path route normalises base64url before `atob`, so a
header carrying `-`/`_` (a `kid`, a signature) is recognised instead of taken
for a path segment; it also checks the three-segment structure.
- Drop the dead stale-key carry-forward in the blocking JWKS path (a stale-good
entry is served by the fast path) and clean up the test's env var.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
A stub issuer that counts inbound connections, ten concurrent cached_jwks calls,
and an assertion that exactly one fetch is made. Pins the property the caching
layer exists for, which no test covered before.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
- Cap the email claim at 254 bytes as well as requiring a plain address. An
over-255-byte email fit the signature but overflowed the guest_activity.email
column: the activity write and the login_guest audit both failed while the
guest was still admitted, so a guest could enter uncounted and unaudited. Now
refused before authentication.
- Serve a stale-but-good JWKS entry while a refresh runs off the request path,
so a slow or hanging issuer no longer stalls guest requests for the fetch
timeout at each 15-minute TTL boundary. Only a cold or negative entry blocks,
still under the single-flight lock; the background refresh no-ops when a fetch
is already in flight and keeps the stale keys on failure.
- Tests: an oversized email is refused alongside the group-shaped and
slash-in-email cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
The email claim becomes the guest's username, and `username_to_permissioned_as`
reads a name with no `@` as `u/<user>` and a `group-` prefix as `g/<group>`. A
token could therefore name the guest after a user or a group. Reject anything
that is not a plain email address (an `@`, no `/`, no whitespace) so a guest's
name is only ever its own principal, never a user's or a group's. Found while
reviewing the identity path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
- Refresh a workspace's JWKS under a per-URL lock, so a burst against a cold or
stale entry triggers one fetch, not one per request (single-flight). The "at
most once per interval" bound now holds while a fetch is in flight, not only
after it lands.
- A cached entry with no keys is a remembered failure; serving it reported an
unreachable issuer as an unknown `kid`. Map an empty entry to an
issuer-unreachable error instead.
- Use the design-system TextInput (textarea variant) for the PEM key field
rather than a raw <textarea>.
- Move `has_any_account` above `username_to_permissioned_as` so it no longer
sits between that function's doc comment and its body.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
- Resolve a guest JWT on the lowercased email. Accounts are stored lowercased,
so a mixed-case `email` claim slipped past the no-account gate and resolved an
account holder to a guest, and split the activity rows the seat count reads.
`has_any_account` now normalises its input too (index-friendly, not
`lower(email)`).
- Cap the auth-cache entry for a guest JWT at 5 minutes rather than the token's
`exp` (up to 24h). A guest JWT is revocable only by the workspace switch or by
rotating the key; the short entry makes a rotated or cleared key bite on
re-verification, and makes the day-keyed activity dedupe reachable across a
midnight (the second-day row was never written).
- Audit `users.login_guest` only when the upsert freshly inserts the row
(`xmax = 0`), decided atomically by the DB, so concurrent first requests and
separate API nodes emit it at most once a day.
- JWKS hardening: read the body with a 1MB cap instead of buffering any size;
an alg-less RSA key accepts the whole RSA family instead of being forced to
RS256; a failed fetch serves the last good keys (or a short negative entry) so
an unreachable issuer is hit at most once per 30s however much unauthenticated
`jwt_guest_` traffic arrives, and an unknown `kid` never triggers a fetch;
lower the fetch timeouts to 5s/10s.
- Settings copy: note that the JWKS should point at an issuer you control, since
neither `iss` nor `aud` is bound.
- Tests: a mixed-case account and an over-24h lifetime are refused; unit tests
pin `jwk_algorithms` (including the alg-less RSA family) and a JWK-derived key
verifying a real token.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
A second way in for a guest (companion to windmill#10929): a JWT the embedding
customer's own backend mints and signs, carried on the app's share link and
verified against a key the workspace admin configured. It needs no
identity-provider round-trip, so it works inside an iframe where popups and
third-party cookies do not. Bearer prefix jwt_guest_, stateless: verified per
request, cached until exp, no token row.
A JWT guest is the same identity as a signed-in guest: no usr row, no password
row, no seat, confined to the one app app_path names. Every guest gate applies:
the plan, the workspace switch (enforced once at the auth door via the sentinel),
the app mode (guest_app_admits), and "no account at all" (has_any_account). The
claim's workspace_id must equal the route's workspace, and a workspace-less route
never accepts it.
Claims honoured: email, workspace_id, app_path, exp (mandatory); nbf/iat
validated when present; the accepted lifetime is capped at 24h. Algorithms:
RS256/384/512, PS256/384/512, ES256/384; HS* is refused. The key is a
per-workspace setting, a PEM public key or a JWKS URL (at most one, a DB CHECK
enforces it), Enterprise-plan gated like the guest switch. The JWKS URL is
validated against private ranges and the fetch is pinned to the validated
address.
Counting: a JWT guest is recorded in guest_activity (once per email, workspace
and day, cached), marked jwt_entry, and not in unique_ext_jwt_token. A first-seen
users.login_guest audit carries the entry kind.
Narrower than jwt_ext_ by design: that key is instance-level and can assert
admin, groups and folders; a guest key is scoped to one workspace and only ever
mints guests. An app-only user a customer routes through jwt_ext_ today is
counted; through a guest JWT they become a free guest, the intended pricing
change, split out as guest_jwt_count in the telemetry so it can be measured.
Changes on the parent branch, additive: ApiAuthed.credential_expiry (a
credential's own expiry when it has no token row); guest_derived_token_constraints
caps on it; guest_session_scopes moved to windmill-api-auth::scopes and
has_any_account to windmill-common::users so the mint and the JWT arm share one
copy; the signed-in mint's login_guest audit now carries entry=idp.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
* feat: make the service log retention period an instance setting
Service log retention was a hardcoded 14 days with no override, unlike job retention. It
becomes the `service_log_retention_secs` global setting (env `SERVICE_LOG_RETENTION_SECS`,
default unchanged at 14 days), reloaded on change like the other retention settings.
The constant becomes `DEFAULT_SERVICE_LOG_RETENTION_SECS` and every reader goes through
`service_log_retention_secs()`, so the `log_file` sweep, the object-storage orphan scan, the
columnar store's compaction and pruning, the retrieval clamp and the search index's trim
window all follow the configured value.
Loaded outside `initial_load`'s `server_mode` guard: a dedicated indexer trims the search
index to a window derived from this value and is not a server.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN
* fix: never let a non-positive service log retention expire every log
Every service log cutoff is `now - retention`, so a `0` or negative window puts the cutoff
at or after `now` and the next sweep reads the whole history as expired — deleting the
`log_file` rows and their object-storage files irreversibly.
`0` is reachable two ways now that the window is configurable: it is what an operator types
by analogy with the job retention period sitting directly above it, where `0` does mean keep
forever; and `SecondsInput` writes a `0` into a field that was merely focused, so saving the
Jobs panel is enough. Service logs always have a window, so clamp an unusable value back to
the default in the accessor every reader already goes through. The upper bound is where
`chrono::Duration::seconds` panics, which would abort the sweep that reads it.
The settings field rejects a non-positive value rather than silently correcting it, and its
description now names the database rows too — they are swept on every instance, including
one with no object storage configured.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN
* fix: address review findings on the service log retention setting
- Bound the monitor's `log_file` sweep. Every process rotates a log file a minute, so lowering
the retention can make one ordinary setting change expire millions of rows; the unbounded
`DELETE ... RETURNING` materialized all of them, and their deletion futures, in a single
tick. Batched like the settings-page cleanup on the same table.
- Make the retention atomic private and give it one writer, so a value that would expire every
service log cannot reach a cutoff by any path, and say so in the log when one is rejected
rather than falling back silently.
- Cap the retention at a century. The previous ceiling only bounded `TimeDelta` construction,
while consumers compute `now - retention`, which panics past year 262143, and build a
Postgres interval that overflows well before the old cap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN
* fix: cap an oversized service log retention instead of shortening it
The two unusable directions were landing on the same fallback, so configuring a retention
above the ceiling silently produced 14 days — deleting logs the operator had asked to keep
for longer. Too large now caps at the maximum, which preserves that intent; only a
non-positive value, which would expire everything and has no upward reading, falls back to
the default.
Also bound the `log_file` drain to ten batches per pass: `monitor_db` runs under a 600s
timeout that cancels every maintenance future in the same `join!` and reports a critical
error, so a backlog large enough to need batching has to drain across ticks, the way the
neighbouring sweeps already do. The settings field carries the upper bound too, and the
superseded query's offline entry is dropped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN
* fix: route the new log-file registration cutoff through the retention accessor
`send_log_files_to_object_store` arrived on main while this branch was open and reads the
retention directly. The atomic behind it is private now, so it goes through the accessor like
every other consumer — which also means the cutoff it uses to skip registering already-expired
files follows the configured retention rather than a fixed two weeks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN
* fix: say why every mode loads the service log retention setting
A worker registers its rotated log files against the retention cutoff, so the comment naming
only the indexer no longer covers why the setting sits outside the `server_mode` guard.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN
* fix: file service log retention under Monitoring, not Jobs
Service logs are the Windmill processes' own logs — every process rotates and registers its
own, no job involved — so the Jobs panel was grouping by the shape of the widget rather than
by the subject. It sits under Monitoring now, beside the Indexer panel that holds the other
service-log window.
Its own section rather than inside that panel: the panel is badged EE, while this governs the
database sweep that runs on every instance.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN
* chore: update ee-repo-ref to a6e3533b26195918a17fea58646f71d2bbcde288
This commit updates the EE repository reference after PR #752 was merged in windmill-ee-private.
Previous ee-repo-ref: 1d93da24bd166b9a5a5cc204034a1d35ffc88474
New ee-repo-ref: a6e3533b26195918a17fea58646f71d2bbcde288
Automated by sync-ee-ref workflow.
* feat: say on the service logs page where the logs actually are
The retention number alone does not tell an operator what it governs, and the answer differs
by instance. Two states are worth calling out because they are the ones where retention does
not mean what it looks like:
Without instance object storage, each process keeps its files on its own disk. The page lists
what every host wrote, since the rows are in the shared database, but can only open the files
of the replica serving the request, and a host's files go with it when it is replaced.
With object storage but "Delete logs from s3 periodically" off — the backend default, since
uploads are gated on a store existing while deletions are gated on that toggle — expiring a
log removes the row and the local file and leaves the uploaded copy behind for good.
The retention field itself now names every copy it covers and says that full-text search
reaches back at most that far, and less when the indexer's own window is shorter.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN
* fix: describe raw log files as the transient copy they became
Retiring the raw files landed while this was being written: the indexer now deletes each one
as soon as it is ingested, and the log viewer rebuilds a file from the columnar store once the
raw copy is gone. So the durable copy is the store, and warning that an uploaded file is kept
forever when periodic s3 deletion is off only holds where no indexer runs to ingest it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN
* chore: point ee-repo-ref at the EE compile fix
EE main does not build on its own: extracting the index-window expression and adding a fourth
copy of it landed in separate PRs that never conflicted textually. windmill-ee-private#756 is
the one-line fix; this pins it so CI has a tree that compiles.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: unify billable seat counting and prevent fork subscriptions
* fix: authorize candidate before reading its plan, scope seat breakdown
* chore: pin ee ref for the stripe checkout fork guard
* fix: grant the billable_member view and widen the paid-plan check
* refactor: keep the seat rule in rust instead of a view and function
* docs: correct the attach guard summary after widening the plan check
* revert: keep cloud out of the ci test feature set
* chore: update ee-repo-ref to 9ff97cd818e85940fec282c92161e98c1b8583e2
This commit updates the EE repository reference after PR #742 was merged in windmill-ee-private.
Previous ee-repo-ref: 0ec0b42565a41f271a45bf24a93467d110c36df3
New ee-repo-ref: 9ff97cd818e85940fec282c92161e98c1b8583e2
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* fix: restrict filesystem workspace storage to debug builds
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7p2VbtYqaXHGaAskgwVk5
* chore: update ee-repo-ref to b58ad414b098d3d7787001a352bfbb13e43a335f
This commit updates the EE repository reference after PR #747 was merged in windmill-ee-private.
Previous ee-repo-ref: 1b4dada77a8fe2224579c643550c63b1ac2616de
New ee-repo-ref: b58ad414b098d3d7787001a352bfbb13e43a335f
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: keep connection string query parameters under token auth
* refactor: fold the database url parsing into one connect-options helper
* docs: state the narrower invariant on base_connect_options
* chore: update ee-repo-ref to 212cc7d61ec38580d4a70d9ac38d7a2cc9daf409
This commit updates the EE repository reference after PR #746 was merged in windmill-ee-private.
Previous ee-repo-ref: a15d08345d7e42526c28382079ad1f575a2d1674
New ee-repo-ref: 212cc7d61ec38580d4a70d9ac38d7a2cc9daf409
Automated by sync-ee-ref workflow.
---------
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat: add instance setting to opt out of zombie job restart alerts
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: preserve explicit false for default-on boolean instance settings
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor: invert zombie restart alert setting to a mute flag
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix: size job token to the premium cloud job timeout
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: give the job token setup headroom and drop dead MAX_TIMEOUT_DURATION
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: cap job token setup slack so self-hosted tokens stay at 7d
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* 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
* chore: bump git-sync hub scripts to windmill-cli 1.794.0
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>