The pass ran inside the monitor's join, whose deadline cancels every
future in it, and a rotation cancelled between GitLab issuing a token and
Windmill storing it loses the token family. A wall-clock budget with a
least-recently-checked ordering kept it under the deadline. Spawning the
pass instead makes the deadline irrelevant, so the budget, the ordering
and the counter go; the advisory lock keeps a slow pass from overlapping
the next, as it already did.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75
Drops the status fingerprint that told one URL token from another, the
docs' promise that such a token's expiry is reported, and the test's
expectation that a URL-token repository declares a host.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75
The maintenance pass sorted repositories with no recorded check first on
the premise that they cost nothing, but a token-in-URL remote on a host
that is not GitLab is probed every pass and never records a check, so it
held the head of the list ahead of the tokens that expire. Such
repositories now sort last.
The card decided its delivery defaults before the origin lookup landed,
so a freshly picked GitLab repository never got webhook delivery; the two
lookups are awaited together. The resource editor offers to replace a
token only where it is held, not in a fork that borrows it, and the
replace flow refuses a URL it cannot parse instead of keying the token to
it. Attaching a stored credential to a commit-hash probe now requires
admin, matching the installation credential beside it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75
* feat: show the new-tab icon on a chat path pill while the modifier is held
Fixes WIN-2477
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAa9nNcYxDN3qAZPrYg4Lq
* fix: read the new-tab modifier in the capture phase
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAa9nNcYxDN3qAZPrYg4Lq
* refactor: track the new-tab modifier only while a pill is hovered
The window key listeners were installed at import time and never removed, so
every page that loaded the module paid for them whether or not a pill existed.
They now attach on mouseenter and detach on mouseleave or destroy, which is the
only window in which the answer is read.
Seeding the flag from the hover event also removes the limitation the previous
version documented: a mouse event carries the same modifier flags as a key
event, so a modifier held before the pointer arrived, or while this window was
unfocused, now reads correctly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAa9nNcYxDN3qAZPrYg4Lq
* refactor: export the new-tab modifier as a read-only view
`newTabModifier` handed every consumer a writable handle on module-global
state, so any of them could drive the icon of every pill on the page. The
getter form is what frontend/AGENTS.md prescribes for shared reactive state.
Tearing each attachment down in the test's afterEach as well: the module state
and its window listeners outlive the DOM, so emptying the body left `held` and
the hovered node set for the following case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAa9nNcYxDN3qAZPrYg4Lq
* refactor: only track the modifier for pills whose icon can change
The attachment went on every path pill, so hovering a drawer or plain-link pill
installed three window listeners for a flag its icon never reads. Only a
preview pill can flip, so only it gets them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAa9nNcYxDN3qAZPrYg4Lq
* fix: re-read the new-tab modifier from pointer movement
A modifier held across a keyboard app switch was cleared by the blur and never
restored: the key was down the whole time so no keydown arrived on the way
back, and the pointer parked on the pill fired no fresh mouseenter either. The
pill then showed the panel icon while the click would have opened a tab.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAa9nNcYxDN3qAZPrYg4Lq
* refactor: give each pill its own modifier state
The shared module state forced a node-identity guard: one hovered element owned
the window listeners, so a pill destroyed elsewhere in the transcript had to be
stopped from tearing them down. A factory per pill removes the guard, its test
case, and the whole class of cross-instance interference, and narrows re-renders
to the hovered pill instead of every preview pill on screen.
Listener teardown now goes through AbortController signals, so leaving a pill
drops the whole set at once rather than through a remove list that has to mirror
every option exactly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAa9nNcYxDN3qAZPrYg4Lq
* fix: abort the previous hover controller on re-entry
A second mouseenter with no mouseleave between replaced the controller without
aborting it, so the four listeners registered under the first signal outlived
even the element's destruction: neither leave nor the destroy path held a
reference to reach them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAa9nNcYxDN3qAZPrYg4Lq
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: write and read python job files as utf-8, not the platform locale
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErBZtEpLpkFi1Y1W6eNZBE
* refactor: trim the PYTHON_UTF8_ENVS comment to the 4-line limit
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErBZtEpLpkFi1Y1W6eNZBE
* chore: bump ee ref for the python runner-group utf8 companion
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErBZtEpLpkFi1Y1W6eNZBE
* chore: update ee-repo-ref to d33ea730c550cdbc7d050aeb6d40dcef3d134e07
This commit updates the EE repository reference after PR #782 was merged in windmill-ee-private.
Previous ee-repo-ref: c8318661f8d91da9172a3c2dca050b70ba7afda2
New ee-repo-ref: d33ea730c550cdbc7d050aeb6d40dcef3d134e07
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(oauth): show the account chooser on Google/Microsoft login
Without `prompt=select_account`, Google and Microsoft silently reuse the single
active browser session, so a user with more than one account has no way to pick
which one to sign in with.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0196aV8v36ukoQcD7L2scvmH
* chore: pin ee ref for the oauth login extra_params fix
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0196aV8v36ukoQcD7L2scvmH
* fix(oauth): only ask for the account chooser on an explicit login click
The login page now sends `user_initiated=true` when someone clicks a
provider button, and the backend applies the provider's `extra_params`
only for those requests.
Someone whose browser holds a single Google session whose email is
already registered under a different login type hits
"an user with the email associated to this login exists but with a
different login type" and, with no account chooser, has no way to offer
a different account. The chooser belongs on that click.
It does not belong on the `auto_login_provider` redirect, whose whole
purpose is to sign a public-app or approval-page visitor in without
interaction.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0196aV8v36ukoQcD7L2scvmH
* fix(oauth): make the account chooser the default, not the opt-in
The login page now flags only the `auto_login_provider` redirect, with
`auto=true`; every other login — a click on a provider button, or the
endpoint opened as a plain URL — gets the provider's extra params.
`/api/oauth/login/*` is whitelisted in `public_app_layer` and reachable
directly, so an opt-in flag would silently drop the account chooser for
every caller that is not our own button.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0196aV8v36ukoQcD7L2scvmH
* chore: update ee-repo-ref to f5d6b6b8dd00b0141308337ac97f4685781f2b1c
This commit updates the EE repository reference after PR #776 was merged in windmill-ee-private.
Previous ee-repo-ref: 5684bb0f63dce08d6ce9ab0183072c8b4fce4b2e
New ee-repo-ref: f5d6b6b8dd00b0141308337ac97f4685781f2b1c
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>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* fix(frontend): render ordered lists in markdown descriptions
`GfmMarkdown` defaulted to `prose-xs`, which Tailwind Typography does not
define — the class only ever matched four hand-rolled rules in app.css, all
scoped to `ul`. Every surface on that default (script and flow descriptions,
flow-graph notes, markdown job results) therefore rendered `<ol>` with
Preflight's `list-style: none` and no typography at all: no numbers, no
heading or paragraph rhythm.
Route the default through the shared `markdownProse` stacks instead, and cut
the app.css list rules down to the dash glyph so ordered and unordered lists
share Tailwind Typography's indentation and rhythm.
Fixes#10971
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6G5gDXJnm6uqch4uCPkPE
* fix(frontend): address review nits on the markdown prose fix
- default `GfmMarkdown` to the `sm` stack rather than `xs`: the AI-agent tool
Message pane takes the default and has no ancestor font size, so `xs` left it
smaller than its own label. The group note, whose wrapper is `text-2xs`, opts
down explicitly.
- regenerate `static/tailwind_full.css`, which raw apps are served and which
still carried the deleted list rules.
- correct the marker-color rationale: the typography config already maps markers
to tertiary, so the rule steps them up rather than rescuing them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6G5gDXJnm6uqch4uCPkPE
* fix(frontend): make the note color override an arbitrary value
`text-inherit` is not generated: this config replaces the Tailwind color palette
outright and defines no `inherit` key, so `[&_*]:!text-inherit` compiled to
nothing and notes still rendered in the prose stack's `text-primary`. Verified in
the browser: a yellow note's list items now compute to `text-yellow-900`, matching
the wrapper and the edit-mode textarea, in both themes.
Also drop the `static/tailwind_full.css` regeneration. That file was generated with
tailwind 3.4.1 against a config predating the typography theme overrides; rebuilding
it today sweeps in 250KB of unrelated churn and would flip every raw app's `.prose`
palette from stock gray to Windmill tokens. Its staleness predates this PR and is
its own change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6G5gDXJnm6uqch4uCPkPE
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: guest JWT entry for embedded apps (jwt_guest_)
A second way in for a guest, alongside the signed-in guest session: a JWT the
embedding customer's backend mints and signs, verified per request against a
per-workspace key (a PEM public key or a JWKS URL), resolving to the same
seatless guest identity confined to the one app its app_path claim names.
Bearer prefix jwt_guest_, stateless (no token row). See PR #10954.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: surface guest JWT as the embed method in the app deploy drawer
The deploy drawer explained the secret-URL embed but not the guest JWT path, so
the primary way to embed an app for a customer's own authenticated users was
undiscoverable. For a guest-mode app with guests enabled, show how to mint a
`jwt_guest_` token and append `guest.<jwt>` to the app URL, with a copyable
iframe template pre-filled with this app's workspace_id and app_path, and a note
that new guest emails are refused past the instance's free allowance (the live
count is shown just above).
Also log a guest JWT allowance refusal at warn, not info: the caller gets a bare
401 (the reason must not leak to an unauthenticated caller), so the log is the
admin's signal that the instance hit its guest cap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: correct the guest JWT minting instructions in the embed block
The block said "sign it with the workspace's guest JWT key", but that setting
holds the public verification key. Clarify the keypair relationship (configure
the public key or a JWKS URL in the workspace; sign with the matching private
key), name the accepted algorithms (RS/PS/ES; HS* refused), and keep the
required claims, so an embedder knows how to actually mint the token.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: fall back to the instance JWT issuer for guest verification (off on cloud)
A workspace with no guest key of its own now verifies guest JWTs against the
instance issuer (JWT_EXT_JWKS_URL, already used by jwt_ext_), so an operator
running one issuer configures it once. Verification and the guest grant are CE;
granting a full login from that issuer stays EE (jwt_ext_, unchanged). Disabled
under CLOUD_HOSTED, where one instance issuer must not be trusted to mint guests
in every tenant's workspace — there the per-workspace key is the only source,
which also stays the override everywhere. The workspace settings note (hidden on
cloud) explains the fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: embed instructions cover both the workspace key and instance issuer
The embed block said to set the workspace's guest JWT key; now it says Windmill
verifies against the workspace key or, off cloud, the instance issuer
(JWT_EXT_JWKS_URL) when no workspace key is set. The instance clause is hidden
under isCloudHosted().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: show the guest JWT embed block only when Embed is toggled
It belongs with the iframe snippet, not the plain-URL view, so gate it on
embedMode alongside the guest-mode / guests-enabled checks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: trust the instance issuer in the guest fallback; refresh stale docs
P1 (CI review): the fallback wrapped JWT_EXT_JWKS_URL as a workspace JwksUrl, so
it hit validate_guest_jwks_url and was refused for http/private issuers unless
ALLOW_PRIVATE_GUEST_JWKS_URLS was also set — a self-hosted internal issuer that
works for jwt_ext_ failed for guests, though the UI says setting the env var is
enough. fetch_jwks now fetches the instance issuer without the https/private
restriction (matching the jwt_ext_ loader; it stays operator-trusted), while a
workspace-admin URL is validated and pinned as before. All the size/key/URL
bounds still apply to both.
P2 (CI review): refresh the stale docs that said a missing workspace key always
refuses a guest JWT — the module, bearer, key-source, and EditGuestJwtKey field
docs now describe the workspace key with the off-cloud instance-issuer fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: fetch the trusted instance issuer like the jwt_ext_ loader
P1 (CI review): the instance-issuer fetch skipped SSRF validation but still
disabled redirects and default cert validation, so an instance issuer that works
for jwt_ext_ through a redirect or an operator-approved self-signed cert failed
the guest fallback. Fetch it with HTTP_CLIENT_PERMISSIVE (follows redirects,
honors ACCEPT_INVALID_CERTS) — the same behavior jwt_ext_ has — while a
workspace-admin URL stays validated, DNS-pinned and redirect-free. The body size
cap still bounds both.
P2 (CI review): the WorkspaceSettings field doc still said None/None means no JWT
guests; it now names the off-cloud instance-issuer fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: schema summary + OpenAPI cover the guest JWT columns and fallback
P2 (CI review): summarized_schema.txt was missing guest_activity.jwt_entry and
the two workspace_settings guest-JWT key columns (required by docs/validation.md
after a schema change). The edit_guest_jwt_key OpenAPI description now notes that
clearing the workspace key falls back to the instance issuer (JWT_EXT_JWKS_URL)
off cloud rather than necessarily stopping guest JWTs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: keep JWKS single-flight locks in a self-cleaning map, not a bounded cache
P1 (CI review): JWKS_FETCH_LOCKS was a 200-entry quick_cache. Past 200 cold URLs
it can evict a lock whose fetch is still in flight; the next request for that URL
then mints a fresh lock and starts a second fetch, so cycling configured
workspaces defeats single-flight and can storm the issuers. Replace it with a
plain map guarded by a JwksFetchLock RAII handle that removes each entry once its
last holder drops, so the map only ever holds the fetches in flight and never
evicts an in-flight lock. Add a unit test pinning the shared-lock and
self-cleaning invariants.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: update ee-repo-ref to c2270eb5fe2d9f0968253e6b460c33186363f4e7
This commit updates the EE repository reference after PR #773 was merged in windmill-ee-private.
Previous ee-repo-ref: 5a1d9dee34159512c0823fddcd3d096490edbcce
New ee-repo-ref: c2270eb5fe2d9f0968253e6b460c33186363f4e7
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: bump the ee ref past the seats_consumed test arity break
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VwxTTC4GsAANBjEZki2cft
* chore: update ee-repo-ref to fb1c5c109846d6c47aff70ab6cc631f4fd773678
This commit updates the EE repository reference after PR #781 was merged in windmill-ee-private.
Previous ee-repo-ref: d197b7b1c76e2aa7cde6cef2e2d9556607cce4c6
New ee-repo-ref: fb1c5c109846d6c47aff70ab6cc631f4fd773678
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>