Repository navigation
fix(cluster): send a terminated cluster's in-cluster bookmarks to its overview - #1954
Merged
dawsontoth merged 4 commits intoOct 14, 2026
Merged
Conversation
… overview A sign-in remembered from before a cluster was terminated let a bookmark to its apps, databases, APIs, operations, status, logs or config page (cluster level and under instance/$instanceId) past checkClusterInstanceAuthenticationBeforeLoad, which returned early on any existing sign-in. The page then sent operations to a host central manager had already torn down. The guard now calls redirectAwayFromDeletedCluster first, before it re-establishes Fabric Connect, checks the remembered sign-in, or auto-connects, so a deleted cluster lands on its overview, which says the cluster is gone. The remembered sign-in is left in place: cleaning it up belongs at cluster scope, not in a child guard that also runs on hover-intent preloads. Closes #1931 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Contributor
There was a problem hiding this comment.
Code Review
This pull request ensures that bookmarks or deep links to in-cluster pages (such as apps, databases, APIs, operations, status, logs, and config) for a terminated cluster are redirected to the overview page before any authentication checks or sign-in restorations occur. This prevents the application from attempting to connect to a torn-down host using remembered credentials. The changes include updating the design documentation, integrating the redirect guard into checkClusterInstanceAuthenticationBeforeLoad, and adding comprehensive unit tests to verify the redirection behavior for both direct page loads and deep links. No review comments were provided, so there is no feedback to address.
dawsontoth
marked this pull request as ready for review
October 14, 2026 16:22
dawsontoth
marked this pull request as draft
October 14, 2026 16:51
… them from its overview The overview called useInstanceAuth(clusterId) before its deleted-cluster branch, so opening a terminated cluster's overview with a remembered sign-in sent user_info to the dead host. ClusterHome now mounts that hook only in ManagedClusterHome, which renders for a live cluster. Nothing ever cleared a terminated cluster's sign-ins either. A leftover one costs a logout to the dead host at every app sign-out, and a stored Basic Auth credential stays in localStorage for good. authStore.signOutEntitiesLocally(ids) signs out each id locally, once per unique id, and never throws. It is called: - by terminateCluster, once the DELETE succeeds: the cluster at once, then each instance read back from the cluster record, since the organization's cluster list carries no instances; - by ClusterTerminated on mount, for a terminate done in another tab, by another member or by central manager. It skips TERMINATING, and a route guard never does this, since guards run on hover preloads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…et the rest from every page Review follow-ups: - A self-hosted cluster removed from Fabric may still be running. Forgetting its sign-ins locally, with no logout, left their cookie sessions open after an app sign-out. terminatedClusterSignIns now returns none for one, so they stay remembered and the next app sign-out still logs them out. - terminateCluster reads the cluster record alongside the DELETE, and reads it back afterwards only if that read failed, so one failed read no longer leaves every instance's credential behind. - The forget moves from ClusterTerminated into ClusterLayout, so every page of a terminated cluster runs it, Usage included. It is keyed on the IDs, not on the polled record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… never fetch from ClusterLayout - When neither cluster read succeeds, terminateCluster can't tell whether the cluster was self-hosted, so it now forgets nothing rather than the cluster's own sign-in. ClusterLayout forgets the sign-ins on the next visit. - ClusterLayout reads the cluster query with enabled: false. It only needs what the route already loaded and what other observers poll, and an enabled observer would refetch on every window focus from in-cluster pages that never watched the cluster. - Comments that restated the code are trimmed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dawsontoth
marked this pull request as ready for review
October 14, 2026 18:21
kriszyp
approved these changes
Oct 14, 2026
dawsontoth
marked this pull request as draft
October 14, 2026 20:54
dawsontoth
marked this pull request as ready for review
October 14, 2026 21:00
dawsontoth
deleted the
claude/1931-terminated-cluster-in-cluster-pages
branch
October 14, 2026 21:04
dawsontoth
added a commit
that referenced
this pull request
Oct 14, 2026
… servers, then forget them A self-hosted cluster's servers may outlive its removal from Fabric, so #1954 kept its remembered sign-ins rather than forget them with no logout. Removing one from Studio now ends those sessions instead. authStore.signOutEntities(ids) starts one best-effort logout per remembered id, built the way app sign-out builds them (the remembered credential, a 10s timeout, no response interceptors), forgets every id locally whatever the servers answer, and never rejects. terminateCluster calls it for a self-hosted cluster's own sign-in and each instance's once the DELETE succeeds, without making the removal wait. A Fabric cluster's terminate is unchanged, and a removed self-hosted cluster is still never cleared on render. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dawsontoth
added a commit
that referenced
this pull request
Oct 15, 2026
… servers, then forget them A self-hosted cluster's servers may outlive its removal from Fabric, so #1954 kept its remembered sign-ins rather than forget them with no logout. Removing one from Studio now ends those sessions instead. authStore.signOutEntities(ids) starts one best-effort logout per remembered id, built the way app sign-out builds them (the remembered credential, a 10s timeout, no response interceptors), forgets every id locally whatever the servers answer, and never rejects. terminateCluster calls it for a self-hosted cluster's own sign-in and each instance's once the DELETE succeeds, without making the removal wait. A Fabric cluster's terminate is unchanged, and a removed self-hosted cluster is still never cleared on render. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
⊙ Problem
A sign-in remembered from before a cluster was terminated still opened its apps, databases, APIs, operations, status, logs and config pages (#1931). Those pages sent operations to hosts central manager had torn down. The overview also checked that sign-in against the dead host, and nothing ever forgot it.
🤔 Should every in-cluster page refuse a terminated cluster?
Yes. #1830 and #1926 already treat a terminated cluster as having nothing left to work with. These pages only talk to its instances. A removed self-hosted cluster needs a decision; see Alternatives.
💡 Solution
/sign-inas the landing page.⚖️ Alternatives
🤔 What about a self-hosted cluster removed from Fabric?
Its in-cluster pages redirect too, but its sign-ins are kept. Its servers may still run. Forgetting them locally would leave their sessions open after an app sign-out. Keeping them lets the next app sign-out log them out. Changing
terminatedClusterSignInswould reverse it.🤔 Should Studio forget nothing when it can't read the cluster?
I chose yes. If both reads around a successful terminate fail, Studio can't tell whether the cluster was self-hosted. It forgets nothing; the next visit to any of its pages clears the sign-ins. The alternative clears the cluster's own sign-in and risks dropping a live self-hosted session.
🔧 Changes
instanceLayoutRoute.ts:checkClusterInstanceAuthenticationBeforeLoadcallsredirectAwayFromDeletedClusterfirst, ahead of the Fabric Connect restore and the sign-in check.redirectAwayFromDeletedCluster.ts's doc comment now covers in-cluster pages.ClusterHome.tsx: the outer component no longer callsuseInstanceAuth; it keeps the cluster query and the branch order. The live managed body, withuseInstanceAuthand the connect callbacks, moves toManagedClusterHome, which only a live cluster renders.authStore.ts: newsignOutEntitiesLocally(ids)runssignOutLocallyonce per unique id and catches each failure. It sits besidesignOutLocallyand leavessignOutAllLocally, which fix(auth): drop a stale API explorer credential when a tab reloads after missing a sign-out #1956 changes, alone.terminatedClusterSignIns.ts: returns the cluster's id and each instance's, or none for a self-hosted cluster.terminateCluster.ts: reads the cluster alongside the DELETE, again after it if that failed, then forgets its sign-ins. The organization's cluster list carries no instances.ClusterLayout.tsx: reads the cluster from the cache without fetching, and forgets the sign-ins once per set of ids when the cluster is terminated and notTERMINATING.cluster/DESIGN.mdrecords the overview rule, the forget points and the self-hosted exception. The root index mentions them.✅ Verification
End-to-end route test (b) renders the whole app. Not tried against a live terminated cluster on stage.
terminatedClusterDeepLink.test.tsxrenders the realAppRoutedand route tree with sign-ins remembered in storage. Cluster and instance bookmarks into aTERMINATEDcluster land on "Cluster Terminated" at the overview, call nouser_info,registration_infoor token mint, and forget every sign-in. A removed self-hosted cluster keeps them.ClusterHome.signIns.test.tsx,ClusterLayout.test.tsx,terminateCluster.test.tsandsignOutEntitiesLocally.test.ts. The guard runs through the real route hooks inredirectAwayFromDeletedCluster.test.tsandrememberedClusterDeepLink.test.ts, which also shows a route load or preload never forgets.vitest --maxWorkers=4(557 files, 5,653 passed),tsc -b,oxlintanddprint checkexit 0 on Node 24.21.0.Fails-on-base and mutation checks
stage: all 112 route-matrix cases fail withexpected undefined to be '/org-1/clu-1', and the router tests stay on the bookmarked page.349e6f8): the overview tests see oneuser_infocall, the terminate and layout tests find every sign-in still stored, and the whole-app test finds them too.useInstanceAuthback in the overview's deleted branch; dropping the layout's forget; forgetting onTERMINATING; keying the forget on the polled record; dropping the self-hosted exception; dropping the read-back after the DELETE; clearing the cluster id when neither read succeeds; dropping the batch's dedupe and its per-id catch, which each have their own test; forgetting from the route guard.The route matrix
TERMINATED,TERMINATING,REMOVED,FAILEDwithterminatedAt.apps,databases,apis,operations,status,logs,configat the cluster level, and the same seven underinstance/ins-1/(apps is the instance index).RUNNINGandFAILEDwithoutterminatedAtstill render every page on the remembered sign-in.Closes #1931
🤖 Generated with Claude Code
Related PRs: #1956 overlaps (both edit
authStore.tsand the rootDESIGN.mdindex; this adds a separate method besidesignOutLocally)Complexity: medium
Review-Coverage: authored=claude; ran=gemini,codex,cursor-composer; adjudicated=domain; declined=cursor-grok,cursor-kimi,cursor-muse,muse; rounds=5; full=1 @ dd9d36f
Review-Attention: study ~15m (sensitive: authStore.ts; decisions: unreadable-cluster-forgets-nothing, layout-owned-cleanup, selfhosted-keeps-signins, read-alongside-plus-retry, do-less-alternative; raised: degraded review) @ dd9d36f