Skip to content

Guard Plugins page reloads against a stale tenant's late fetch - #442

Merged
TheGreatAxios merged 4 commits into
mainfrom
cl-7138-plugins-page-can-show-the-previous-benchs-pluginsskills
Aug 29, 2026
Merged

TheGreatAxios merged 4 commits into
mainfrom
cl-7138-plugins-page-can-show-the-previous-benchs-pluginsskills

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes CL-7138 — https://linear.app/abklabs/issue/CL-7138

Problem

apps/web/src/pages/plugins-page.tsx:95-121 ran reloadPlugins/reloadSkills as .then(setState).catch(setState) chains with no cancellation. Both effects re-ran on selectedTenantId, but a fast bench switch could leave the previous tenant's fetch in flight; if it resolved after the new tenant's fetch, its response overwrote the newly selected tenant's state, so the Plugins page could momentarily (or persistently, on a slow response) show the wrong bench's plugins/skills.

Change

  • Both reload effects now track a cancelled flag set in their cleanup (the pattern already used in packages/settings-ui/src/people-section.tsx:106-154) and only call setState when !cancelled.
  • Restructured so each effect owns its own fetch, keyed on selectedTenantId plus a pluginsReloadKey/skillsReloadKey counter; the existing imperative reloadPlugins/reloadSkills call sites (connect/disconnect panel, retry button, skill creation) now just bump that counter, so they still refresh the current tenant without duplicating fetch logic outside the effect.
  • Each effect also tracks the tenant it last fetched for in a ref, and only sets status: "loading" when selectedTenantId actually changed — a same-tenant reload (connect/disconnect, Retry) keeps whatever is on screen and swaps in the fresh result when it lands, instead of tearing the page down to the full loading skeleton on every reload.
  • No copy changes.

Tests

apps/web/test/plugins-page.test.tsx:

  • Mounts the page on tenant A (skills fetch deferred), switches to tenant B before A resolves, resolves B's fetch first and A's late — asserts the page ends up showing B's skill and never A's. Confirmed this fails against the pre-fix code and passes against the fix.
  • Drives the real GitHub disconnect flow (Manage → Disconnect → confirm), deferring the reload's fetch, and asserts the gallery still shows its prior content (not the "Loading plugins…" skeleton) while the reload is in flight, then shows the disconnected result once it lands. Confirmed this fails without the loading-skeleton guard and passes with it.

cd apps/web && bun test ./test/plugins-page.test.tsx — 9 pass.
cd apps/web && bun run typecheck — clean.
bunx prettier --write + bunx eslint on both changed files — clean.

Full bun run check not run locally (shared-machine load).

Proves a fast bench switch doesn't let a slower tenant's late plugins/skills fetch overwrite the newly selected tenant's state.
reloadPlugins/reloadSkills had no cancellation, so switching benches while
a fetch was still in flight could let the previous tenant's response land
after the new tenant's and overwrite its state. Both effects now track a
cancelled flag in their cleanup, the same pattern people-section.tsx
already uses, and the effect itself owns re-fetching (via a reload key)
so the existing imperative reloadPlugins/reloadSkills call sites still
refresh the current tenant.

Fixes CL-7138.
…leton

Proves a connect/disconnect-triggered reload keeps the gallery on screen and swaps in fresh data when it lands, instead of tearing down to the full loading skeleton on every imperative reload.
The reload-key restructure from the previous commit made every imperative
reload (a connect/disconnect panel's onChanged, the error screen's Retry)
re-run the fetch effect, which unconditionally set status: "loading" and
tore the page down to the full skeleton — a regression from main, where an
imperative reload just fetched and swapped in the result. Each effect now
tracks the tenant it last fetched for in a ref and only shows the loading
state when selectedTenantId actually changed; a same-tenant reload keeps
whatever is on screen until the fresh data (or a new error) lands.

Fixes CL-7138.
@TheGreatAxios
TheGreatAxios merged commit 64ff65a into main Aug 29, 2026
5 checks passed
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.

1 participant