Skip to content

fix(environments): finish module on real IBackendApiClientProvider - #1042

Merged
sfmskywalker merged 3 commits into
mainfrom
cursor/environments-backend-provider-565e
Sep 15, 2026
Merged

sfmskywalker merged 3 commits into
mainfrom
cursor/environments-backend-provider-565e

Conversation

@sfmskywalker

@sfmskywalker sfmskywalker commented Sep 14, 2026

Copy link
Copy Markdown
Member

Purpose

Finish Elsa.Studio.Environments on the supported IBackendApiClientProvider path (Option B — do not remove). Closes #1039. Related: #326.


Scope

Select one primary concern:

  • Bug fix (behavior change)
  • Refactor (no behavior change)
  • Documentation update
  • Formatting / code cleanup
  • Dependency / build update
  • New feature

Description

Problem

EnvironmentBackendApiClientProvider built a private ServiceCollection/ServiceProvider on every GetApiAsync call, left auth commented out, and replaced DefaultBackendApiClientProvider. LoadEnvironmentsStartupTask was commented out, so IEnvironmentService stayed empty and EnvironmentPicker never filled (#326).

Solution

Option B — reuse the real client stack:

  1. Replace IRemoteBackendAccessor with EnvironmentRemoteBackendAccessor (selected environment URL, else BackendOptions).
  2. Keep DefaultBackendApiClientProvider so ApiClientFactory + Blazor scope proxy + host auth still apply.
  3. Delete EnvironmentBackendApiClientProvider.
  4. Register LoadEnvironmentsStartupTask and run it from Feature.InitializeAsync so circuit-scoped pickers actually load.
  5. Key RemoteFeatureProvider catalog by backend URL after GetApiAsync inside the catalog lock, so an env switch mid-load cannot store backend B's catalog under backend A's key.

Sample hosts stay opt-in (commented AddEnvironmentsModule) because /environments is not part of the default backend.


Verification

Steps:

  1. dotnet test src/modules/Elsa.Studio.Environments.Tests/Elsa.Studio.Environments.Tests.csproj — 6 passed
  2. dotnet test src/modules/Elsa.Studio.ExternalAuthentication.Tests --filter RemoteFeatureProviderTests — 9 passed (includes mid-load URL switch)
  3. Call AddEnvironmentsModule(backendApiConfig) on a host whose backend exposes /environments and confirm EnvironmentPicker fills after login.

Expected outcome:

  • IBackendApiClientProvider remains DefaultBackendApiClientProvider.
  • Switching environment changes IRemoteBackendAccessor.RemoteBackend.Url and subsequent API calls.
  • Environments load into IEnvironmentService after the startup task / feature init.
  • Feature catalog cache key matches the URL read after GetApiAsync under the lock.

Checklist

  • The PR is focused on a single concern
  • Commit messages follow the recommended convention
  • Tests added or updated (if applicable)
  • Documentation updated (if applicable)
  • No unrelated cleanup included
  • All tests pass
Open in Web Open in Cursor 

cursoragent and others added 2 commits September 14, 2026 23:07
Reuse DefaultBackendApiClientProvider by swapping IRemoteBackendAccessor
for the selected environment URL, restore LoadEnvironmentsStartupTask,
and delete the private ServiceProvider client factory (#1039, #326).

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
A missing environments API must not stop other studio features from
initializing or leave the picker unregistered.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
@sfmskywalker
sfmskywalker marked this pull request as ready for review September 14, 2026 23:09
Comment on lines +174 to +178
return Task.FromResult(new HttpResponseMessage(StatusCode)
{
RequestMessage = request,
Content = new StringContent(ResponseContent, Encoding.UTF8, new MediaTypeHeaderValue("application/json"))
});
Comment thread src/modules/Elsa.Studio.Environments/Module.cs Fixed
@greptile-apps

greptile-apps Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

Safe to merge.

Summary

This change completes environment-aware backend switching while retaining the authenticated API-client pipeline. It loads environments during startup and feature initialization, handles expected unavailable or unauthorized responses with logging, and caches remote feature catalogs by the selected backend URL.

Reviews (2) · Last reviewed commit: "fix(core): key remote feature catalog af..."

Comment thread src/framework/Elsa.Studio.Core/Services/RemoteFeatureProvider.cs Outdated
Comment thread src/modules/Elsa.Studio.Environments/Module.cs Outdated

@sfmskywalker sfmskywalker left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Round 1/4 @ 56c8ed2main

Verdict: REQUEST_CHANGES + HIGH (formal event COMMENT — own-PR APPROVE blocked for sfmskywalker)

What looks good (Option B for #1039)

  • Delete EnvironmentBackendApiClientProvider (private ServiceProvider / auth-stripped path).
  • EnvironmentRemoteBackendAccessor + keep DefaultBackendApiClientProvider.
  • Register LoadEnvironmentsStartupTask / circuit Feature.InitializeAsync load; missing /environments no longer blocks other features.
  • Hosts stay opt-in (commented AddEnvironmentsModule).
  • Tests cover Default provider kept, load/fill, missing API, env switch updates accessor URL, feature catalog refetch on URL change.

Blocking — RemoteFeatureProvider catalog URL TOCTOU (Greptile P1)

backendUrl is captured before _catalogLock. Inside the lock, GetApiAsync reads the live environment URL again. If the env switches between those reads, catalog B can be stored under _catalogUrl = A (or the reverse), so later checks for B hit a stale/wrong cache.

Fix: re-read remoteBackendApiClientProvider.Url inside the lock (after the double-check) and assign _catalogUrl from that same value after the fetch (ideally re-read again after GetApiAsync and key by the URL that was actually used). Add/adjust a unit test for switch-during-load if easy.

Soft steers (non-blocking)

  • Empty catch (ApiException) in Feature.InitializeAsync — add the same clarifying comment as the HttpRequestException arm (CodeQL).
  • Greptile P2: optional logging on swallowed load failures for ops visibility.
  • Test HttpResponseMessage dispose nit (CodeQL).

Merge gates

  • Code Review: REQUEST_CHANGES @ 56c8ed2 — re-request after P1 fix
  • Greptile: tip score unknown/credits low — when unavailable, merge would be CR APPROVE+HIGH ∧ CI green (CI currently green)
  • CI: green on tip

Read IBackendApiClientProvider.Url inside the catalog lock after
creating the client so an environment switch mid-load cannot store
backend B's catalog under backend A's cache key.

Also log expected environments load failures (404/401/403) instead of
swallowing every ApiException.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
@sfmskywalker

Copy link
Copy Markdown
Member Author

@greptileai

@sfmskywalker sfmskywalker left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Round 2/4 @ 9dcc42cmain

Verdict: APPROVE + HIGH (formal event COMMENT — own-PR APPROVE blocked for sfmskywalker)

Prior Round 1 @ 56c8ed2 is STALE. Soft steers parked.

Delta vs Round 1

  • TOCTOU fixed: Under _catalogLock, GetApiAsync then read IBackendApiClientProvider.Url as _catalogUrl (no pre-lock capture). Double-check uses that post-fetch URL.
  • Test: FeatureChecks_DoNotCacheUnderStaleUrlWhenEnvironmentChangesMidLoad.
  • Soft/P2: Feature + startup task only swallow HttpRequestException and ApiException 404/401/403, with warning logs.

Blocking Round 1 note addressed. Clear and maintainable.

Soft steers (non-blocking)

  • GetApiAsync now runs before the in-lock double-check (extra client mint when cache already warm for the same URL) — acceptable; reorder only if profiling cares.
  • Unexpected ApiException (e.g. 500) on Feature.InitializeAsync now propagates before AddElement — intentional narrowing; watch host feature-init behavior.

Merge gates

  • Code Review: APPROVE + HIGH @ 9dcc42c
  • Greptile: tip 5/5 while scoring; if Greptile stalls/credits out → APPROVE+HIGH ∧ CI green is enough
  • CI: green on tip

@sfmskywalker
sfmskywalker merged commit cf3f781 into main Sep 15, 2026
10 checks passed
@sfmskywalker
sfmskywalker deleted the cursor/environments-backend-provider-565e branch September 15, 2026 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Environments module: finish on real IBackendApiClientProvider (not remove)

2 participants