From 6f204605ad64e372bca2b4031eab25e242b00dc2 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:55:54 -0500 Subject: [PATCH] fix(ui): show source SHA for unreleased builds (#9508) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source control plane people use to manage AI-agent companies > - Operators need to identify the exact build running from the persistent account menu > - Formal releases already have a concise public version, but source builds include a long derived version string > - The derived version identifies a commit but does not expose the source branch or a direct path to inspect the code > - Server Git metadata is auth-sensitive, so the UI must also refresh it when the current session changes > - This pull request shows linked branch and commit metadata for source builds while preserving `v` for formal releases > - The benefit is faster build diagnosis with correct metadata across sign-in and sign-out transitions ## Linked Issues or Issue Description ### Pre-submission checklist - [x] I searched existing open and closed issues and found no duplicate implementing this exact account-menu behavior. - [x] The behavior reproduces on `master`. - [x] The behavior originates in Paperclip's core UI, not an adapter, provider, or local configuration. ### What happened? Source builds displayed the full derived version, such as `2026.626.0+58.git.518fc71ce`, without linking the operator to the corresponding source branch or commit. ### Expected behavior Source builds should show the concise branch and short commit SHA with links to GitHub, while formal releases should continue showing their public version. Auth transitions should refresh the health metadata that supplies those Git details. ### Steps to reproduce 1. Run Paperclip from a commit after a release tag. 2. Open the account menu. 3. Inspect the build label beneath the user identity. 4. Sign in or out and reopen the menu. ### Paperclip version or commit Any source build whose server version uses the `+.git.[.dirty]` format. ### Deployment mode Local dev (`pnpm dev`) or authenticated deployments. ### Installation method Built from source. ## What Changed - Detect source-derived version strings and render the source branch plus seven-character commit SHA in `SidebarAccountMenu`. - Link source branches and commits to the canonical `paperclipai/paperclip` GitHub repository. - Extend server Git metadata with the full SHA and expose it through health/OpenAPI contracts. - Refresh auth-sensitive health metadata after sign-in and every sign-out entry point. - Preserve the existing `v` label for formal releases and add focused regression coverage. ## Verification - `pnpm --filter @paperclipai/ui exec vitest run src/pages/Auth.test.tsx src/components/SidebarAccountMenu.test.tsx src/components/SidebarServerInfo.test.tsx` - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/health.test.ts src/__tests__/server-info.test.ts` - `pnpm --filter @paperclipai/ui typecheck` - `pnpm check:token-gates` - `git diff --check public/master...HEAD` ## Risks - Low risk: formal release rendering retains the existing fallback behavior when the source-version pattern does not match. - Source links assume the build came from the canonical public repository; fork-only branches or commits may not resolve there. - Health metadata is invalidated after auth transitions, adding one bounded refetch so the displayed Git details match the new session. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex using GPT-5.4 with medium reasoning, repository/tool access, shell execution, and code editing; context-window size was not exposed by the runtime. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- packages/shared/src/types/server-info.ts | 1 + server/src/__tests__/health.test.ts | 1 + server/src/__tests__/server-info.test.ts | 25 +++++++ server/src/routes/openapi.ts | 1 + server/src/server-info.ts | 36 ++++++++-- ui/src/components/Layout.tsx | 2 + ui/src/components/SidebarAccountMenu.test.tsx | 65 +++++++++++++++++++ ui/src/components/SidebarAccountMenu.tsx | 44 ++++++++++++- ui/src/components/SidebarCompanyMenu.tsx | 1 + ui/src/components/SidebarServerInfo.test.tsx | 5 ++ ui/src/pages/Auth.test.tsx | 48 ++++++++++++-- ui/src/pages/Auth.tsx | 1 + ui/src/pages/InstanceGeneralSettings.tsx | 5 +- ui/src/pages/InviteLanding.tsx | 1 + 14 files changed, 222 insertions(+), 14 deletions(-) diff --git a/packages/shared/src/types/server-info.ts b/packages/shared/src/types/server-info.ts index 852404d0cd..5c9ae793bb 100644 --- a/packages/shared/src/types/server-info.ts +++ b/packages/shared/src/types/server-info.ts @@ -18,6 +18,7 @@ export type ServerGitInfo = available: true; fullSha: string; shortSha: string; + branchName: string | null; subject: string; committedAt: string | null; localChanges: ServerGitLocalChanges; diff --git a/server/src/__tests__/health.test.ts b/server/src/__tests__/health.test.ts index ab2eeb0e38..9fbc47f3b4 100644 --- a/server/src/__tests__/health.test.ts +++ b/server/src/__tests__/health.test.ts @@ -16,6 +16,7 @@ const testServerInfo = { available: true, fullSha: "0123456789abcdef0123456789abcdef01234567", shortSha: "0123456", + branchName: "master", subject: "Add server info debug view", committedAt: "2026-06-25T23:00:00.000Z", localChanges: { diff --git a/server/src/__tests__/server-info.test.ts b/server/src/__tests__/server-info.test.ts index a64b62bece..1af391d431 100644 --- a/server/src/__tests__/server-info.test.ts +++ b/server/src/__tests__/server-info.test.ts @@ -21,6 +21,7 @@ describe("server info snapshot", () => { "Add server info debug view", "2026-06-25T17:00:00-07:00", ].join("\n"), + gitBranchCommand: () => "feature/server-info\n", gitStatusCommand: () => "", }); @@ -30,6 +31,7 @@ describe("server info snapshot", () => { available: true, fullSha: "0123456789abcdef0123456789abcdef01234567", shortSha: "0123456", + branchName: "feature/server-info", subject: "Add server info debug view", committedAt: "2026-06-26T00:00:00.000Z", localChanges: { @@ -98,6 +100,29 @@ describe("server info snapshot", () => { }); }); + it("keeps commit metadata available when HEAD is detached", () => { + const snapshot = createServerInfoSnapshot({ + now: new Date("2026-06-26T00:00:00.000Z"), + gitCommand: () => + [ + "0123456789abcdef0123456789abcdef01234567", + "0123456", + "Add server info debug view", + "2026-06-25T17:00:00-07:00", + ].join("\n"), + gitBranchCommand: () => { + throw new Error("detached HEAD"); + }, + gitStatusCommand: () => "", + }); + + expect(snapshot.git).toMatchObject({ + available: true, + branchName: null, + shortSha: "0123456", + }); + }); + it("uses sanitized fallback metadata when git is unavailable", () => { const snapshot = createServerInfoSnapshot({ now: new Date("2026-06-26T00:00:00.000Z"), diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index 1c485cda35..1ad9b00cf1 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -930,6 +930,7 @@ registry.registerPath({ available: z.literal(true), fullSha: z.string(), shortSha: z.string(), + branchName: z.string().nullable(), subject: z.string(), committedAt: z.string().datetime().nullable(), localChanges: z.union([ diff --git a/server/src/server-info.ts b/server/src/server-info.ts index 369cb1435a..eb386be6c3 100644 --- a/server/src/server-info.ts +++ b/server/src/server-info.ts @@ -32,6 +32,18 @@ function defaultGitStatusCommand() { ); } +function defaultGitBranchCommand() { + return execFileSync( + "git", + ["symbolic-ref", "--quiet", "--short", "HEAD"], + { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + timeout: 1500, + }, + ); +} + function parseGitLocalChanges(output: string): ServerGitLocalChanges { let stagedFileCount = 0; let unstagedFileCount = 0; @@ -67,7 +79,11 @@ function getGitLocalChanges(gitStatusCommand: GitCommand): ServerGitLocalChanges } } -function parseGitInfo(output: string, localChanges: ServerGitLocalChanges): ServerGitInfo { +function parseGitInfo( + output: string, + branchName: string | null, + localChanges: ServerGitLocalChanges, +): ServerGitInfo { const [fullSha = "", shortSha = "", subject = "", committedAt = ""] = output .trimEnd() .split("\n"); @@ -81,6 +97,7 @@ function parseGitInfo(output: string, localChanges: ServerGitLocalChanges): Serv available: true, fullSha, shortSha, + branchName, subject: subject.trim() || "No commit subject", committedAt: Number.isNaN(committedAtTime) ? null : new Date(committedAtTime).toISOString(), localChanges, @@ -90,22 +107,29 @@ function parseGitInfo(output: string, localChanges: ServerGitLocalChanges): Serv function readGitInfo( gitCommand: GitCommand = defaultGitCommand, gitStatusCommand: GitCommand = defaultGitStatusCommand, + gitBranchCommand: GitCommand = defaultGitBranchCommand, ): ServerGitInfo { try { const output = gitCommand(); const localChanges = getGitLocalChanges(gitStatusCommand); - return parseGitInfo(output, localChanges); + let branchName: string | null = null; + try { + branchName = gitBranchCommand().trim() || null; + } catch { + branchName = null; + } + return parseGitInfo(output, branchName, localChanges); } catch { return { available: false, unavailableReason: "git_unavailable" }; } } export function createServerInfoSnapshot( - opts: { now?: Date; gitCommand?: GitCommand; gitStatusCommand?: GitCommand } = {}, + opts: { now?: Date; gitCommand?: GitCommand; gitStatusCommand?: GitCommand; gitBranchCommand?: GitCommand } = {}, ): ServerInfoSnapshot { return { processStartedAt: (opts.now ?? new Date()).toISOString(), - git: readGitInfo(opts.gitCommand, opts.gitStatusCommand), + git: readGitInfo(opts.gitCommand, opts.gitStatusCommand, opts.gitBranchCommand), }; } @@ -119,12 +143,12 @@ const processStartedAt = new Date().toISOString(); let gitInfoCache: { value: ServerGitInfo; expiresAt: number } | null = null; export function getServerInfoSnapshot( - opts: { now?: number; gitCommand?: GitCommand; gitStatusCommand?: GitCommand } = {}, + opts: { now?: number; gitCommand?: GitCommand; gitStatusCommand?: GitCommand; gitBranchCommand?: GitCommand } = {}, ): ServerInfoSnapshot { const now = opts.now ?? Date.now(); if (!gitInfoCache || now >= gitInfoCache.expiresAt) { gitInfoCache = { - value: readGitInfo(opts.gitCommand, opts.gitStatusCommand), + value: readGitInfo(opts.gitCommand, opts.gitStatusCommand, opts.gitBranchCommand), expiresAt: now + GIT_INFO_CACHE_TTL_MS, }; } diff --git a/ui/src/components/Layout.tsx b/ui/src/components/Layout.tsx index c694b81fbf..14b6f221fd 100644 --- a/ui/src/components/Layout.tsx +++ b/ui/src/components/Layout.tsx @@ -549,6 +549,7 @@ export function Layout() { @@ -568,6 +569,7 @@ export function Layout() { diff --git a/ui/src/components/SidebarAccountMenu.test.tsx b/ui/src/components/SidebarAccountMenu.test.tsx index 317d174319..bf36a3e5b4 100644 --- a/ui/src/components/SidebarAccountMenu.test.tsx +++ b/ui/src/components/SidebarAccountMenu.test.tsx @@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { queryKeys } from "../lib/queryKeys"; import { SidebarAccountMenu } from "./SidebarAccountMenu"; const mockAuthApi = vi.hoisted(() => ({ @@ -85,6 +86,7 @@ describe("SidebarAccountMenu", () => { mockInstanceSettingsApi.getExperimental.mockResolvedValue({ enableIsolatedWorkspaces: false, }); + mockAuthApi.signOut.mockResolvedValue(undefined); }); afterEach(() => { @@ -98,6 +100,10 @@ describe("SidebarAccountMenu", () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); + queryClient.setQueryData(queryKeys.health, { + status: "ok", + deploymentMode: "authenticated", + }); await act(async () => { root.render( @@ -147,6 +153,65 @@ describe("SidebarAccountMenu", () => { .toContain("w-(--sz-277px)"); expect(document.body.querySelector('a[href="/company/settings/instance/profile"]')).not.toBeNull(); + const signOutButton = Array.from(document.body.querySelectorAll("button")).find( + (button) => button.textContent?.includes("Sign out"), + ); + await act(async () => { + signOutButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + expect(mockAuthApi.signOut).toHaveBeenCalledOnce(); + expect(queryClient.getQueryState(queryKeys.health)?.isInvalidated).toBe(true); + + await act(async () => { + root.unmount(); + }); + }); + + it("shows the short commit sha instead of a version for source builds", async () => { + const root = createRoot(container); + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + + await act(async () => { + root.render( + + + , + ); + }); + await flushReact(); + + expect(document.body.textContent).toContain("feature/source-build-labelPaperclip 518fc71"); + expect(document.body.textContent).not.toContain("2026.626.0+58.git.518fc71ce"); + expect(document.body.querySelector('a[href="https://github.com/paperclipai/paperclip/tree/feature%2Fsource-build-label"]')?.textContent).toBe( + "feature/source-build-label", + ); + expect(document.body.querySelector('a[href="https://github.com/paperclipai/paperclip/commit/518fc71ce1234567890abcdef1234567890abcde"]')?.textContent).toBe( + "518fc71", + ); + await act(async () => { root.unmount(); }); diff --git a/ui/src/components/SidebarAccountMenu.tsx b/ui/src/components/SidebarAccountMenu.tsx index 17a9394a26..f2dd6d439a 100644 --- a/ui/src/components/SidebarAccountMenu.tsx +++ b/ui/src/components/SidebarAccountMenu.tsx @@ -8,7 +8,7 @@ import { UserRound, UserRoundPen, } from "lucide-react"; -import type { DeploymentMode } from "@paperclipai/shared"; +import type { DeploymentMode, ServerGitInfo } from "@paperclipai/shared"; import { Link } from "@/lib/router"; import { authApi } from "@/api/auth"; import { queryKeys } from "@/lib/queryKeys"; @@ -23,11 +23,14 @@ import { Badge } from "@/components/ui/badge"; const PROFILE_SETTINGS_PATH = "/company/settings/instance/profile"; const DOCS_URL = "https://docs.paperclip.ing/"; const FEEDBACK_URL = "https://paperclip.ing/feedback"; +const SOURCE_REPOSITORY_URL = "https://github.com/paperclipai/paperclip"; +const SOURCE_VERSION_RE = /\+\d+\.git\.([0-9a-f]{7,40})(?:\.dirty)?$/i; interface SidebarAccountMenuProps { deploymentMode?: DeploymentMode; open?: boolean; onOpenChange?: (open: boolean) => void; + serverGit?: ServerGitInfo; version?: string | null; } @@ -62,6 +65,11 @@ function deriveUserSlug(name: string | null | undefined, email: string | null | return "me"; } +function sourceVersionSha(version: string): string | null { + const sourceVersion = version.match(SOURCE_VERSION_RE); + return sourceVersion?.[1] ?? null; +} + function MenuAction({ label, description, icon: Icon, onClick, href, external = false }: MenuActionProps) { const className = "flex w-full items-start gap-3 rounded-xl px-3 py-3 text-left transition-colors hover:bg-accent/60"; @@ -105,6 +113,7 @@ export function SidebarAccountMenu({ deploymentMode, open: controlledOpen, onOpenChange, + serverGit, version, }: SidebarAccountMenuProps) { const [internalOpen, setInternalOpen] = useState(false); @@ -124,6 +133,7 @@ export function SidebarAccountMenu({ onSuccess: async () => { setOpen(false); await queryClient.invalidateQueries({ queryKey: queryKeys.auth.session }); + await queryClient.invalidateQueries({ queryKey: queryKeys.health }); }, }); @@ -133,6 +143,12 @@ export function SidebarAccountMenu({ const accountBadge = deploymentMode === "authenticated" ? "Account" : "Local"; const initials = deriveInitials(displayName); const profileHref = `/u/${deriveUserSlug(session?.user.name, session?.user.email, session?.user.id)}`; + const sourceSha = version ? sourceVersionSha(version) : null; + const sourceFullSha = + sourceSha && serverGit?.available && serverGit.fullSha.toLowerCase().startsWith(sourceSha.toLowerCase()) + ? serverGit.fullSha + : sourceSha; + const sourceBranch = sourceSha && serverGit?.available ? serverGit.branchName : null; function closeNavigationChrome() { setOpen(false); @@ -178,7 +194,31 @@ export function SidebarAccountMenu({

{secondaryLabel}

- {version ? ( + {sourceSha && sourceFullSha ? ( +
+ {sourceBranch ? ( + + {sourceBranch} + + ) : null} +

+ Paperclip{" "} + + {sourceSha.slice(0, 7)} + +

+
+ ) : version ? (

Paperclip v{version}

) : null} diff --git a/ui/src/components/SidebarCompanyMenu.tsx b/ui/src/components/SidebarCompanyMenu.tsx index 75da3d1cd2..a8340528fa 100644 --- a/ui/src/components/SidebarCompanyMenu.tsx +++ b/ui/src/components/SidebarCompanyMenu.tsx @@ -169,6 +169,7 @@ export function SidebarCompanyMenu({ open: controlledOpen, onOpenChange }: Sideb setOpen(false); if (isMobile) setSidebarOpen(false); await queryClient.invalidateQueries({ queryKey: queryKeys.auth.session }); + await queryClient.invalidateQueries({ queryKey: queryKeys.health }); }, }); diff --git a/ui/src/components/SidebarServerInfo.test.tsx b/ui/src/components/SidebarServerInfo.test.tsx index 5f4f8ef718..7b29ef1402 100644 --- a/ui/src/components/SidebarServerInfo.test.tsx +++ b/ui/src/components/SidebarServerInfo.test.tsx @@ -112,6 +112,7 @@ describe("SidebarServerInfo", () => { available: true, fullSha: "abcdef1234567890abcdef1234567890abcdef12", shortSha: "abcdef1", + branchName: "master", subject: "Add server info debug view", committedAt: "2026-06-25T23:00:00.000Z", localChanges: { @@ -160,6 +161,7 @@ describe("SidebarServerInfo", () => { available: true, fullSha: "abcdef1234567890abcdef1234567890abcdef12", shortSha: "abcdef1", + branchName: "master", subject: "Add server info debug view", committedAt: "2026-06-25T23:00:00.000Z", localChanges: { @@ -204,6 +206,7 @@ describe("SidebarServerInfo", () => { available: true, fullSha: "abcdef1234567890abcdef1234567890abcdef12", shortSha: "abcdef1", + branchName: "master", subject: "Add server info debug view", committedAt: "2026-06-25T23:00:00.000Z", localChanges: { @@ -233,6 +236,7 @@ describe("SidebarServerInfo", () => { available: true, fullSha: "1111111111111111111111111111111111111111", shortSha: "1111111", + branchName: "master", subject: "First boot", committedAt: "2026-06-25T23:00:00.000Z", localChanges: { @@ -279,6 +283,7 @@ describe("SidebarServerInfo", () => { available: true, fullSha: "2222222222222222222222222222222222222222", shortSha: "2222222", + branchName: "master", subject: "After restart", committedAt: "2026-06-26T01:30:00.000Z", localChanges: { diff --git a/ui/src/pages/Auth.test.tsx b/ui/src/pages/Auth.test.tsx index 47be067637..59d70be809 100644 --- a/ui/src/pages/Auth.test.tsx +++ b/ui/src/pages/Auth.test.tsx @@ -5,6 +5,7 @@ import { createRoot } from "react-dom/client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { MemoryRouter, Route, Routes } from "react-router-dom"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { queryKeys } from "../lib/queryKeys"; import { AuthPage } from "./Auth"; const getSessionMock = vi.hoisted(() => vi.fn()); @@ -110,11 +111,11 @@ describe("AuthPage", () => { }); await flushReact(); await flushReact(); - return root; + return { root, queryClient }; } it("exposes password-manager metadata and a11y attributes on the sign-in form", async () => { - const root = await mount(); + const { root } = await mount(); const emailInput = container.querySelector('input[name="email"]') as HTMLInputElement; const passwordInput = container.querySelector('input[name="password"]') as HTMLInputElement; @@ -147,7 +148,7 @@ describe("AuthPage", () => { }); it("uses new-password autocomplete in sign-up mode", async () => { - const root = await mount(); + const { root } = await mount(); const createOne = Array.from(container.querySelectorAll("button")).find( (button) => button.textContent === "Create one", @@ -172,7 +173,7 @@ describe("AuthPage", () => { }); it("renders auth errors in an assertive alert region referenced by the inputs", async () => { - const root = await mount(); + const { root } = await mount(); const inputValueSetter = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, @@ -213,4 +214,43 @@ describe("AuthPage", () => { root.unmount(); }); }); + + it("invalidates anonymous health metadata after sign-in", async () => { + const { root, queryClient } = await mount(); + queryClient.setQueryData(queryKeys.health, { + status: "ok", + deploymentMode: "authenticated", + }); + + const inputValueSetter = Object.getOwnPropertyDescriptor( + HTMLInputElement.prototype, + "value", + )?.set; + const emailInput = container.querySelector('input[name="email"]') as HTMLInputElement; + const passwordInput = container.querySelector('input[name="password"]') as HTMLInputElement; + + await act(async () => { + inputValueSetter!.call(emailInput, "jane@example.com"); + emailInput.dispatchEvent(new Event("input", { bubbles: true })); + inputValueSetter!.call(passwordInput, "supersecret"); + passwordInput.dispatchEvent(new Event("input", { bubbles: true })); + }); + + const form = container.querySelector("form") as HTMLFormElement; + await act(async () => { + form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true })); + }); + await flushReact(); + await flushReact(); + + expect(signInEmailMock).toHaveBeenCalledWith({ + email: "jane@example.com", + password: "supersecret", + }); + expect(queryClient.getQueryState(queryKeys.health)?.isInvalidated).toBe(true); + + await act(async () => { + root.unmount(); + }); + }); }); diff --git a/ui/src/pages/Auth.tsx b/ui/src/pages/Auth.tsx index 20a8c542db..6948254782 100644 --- a/ui/src/pages/Auth.tsx +++ b/ui/src/pages/Auth.tsx @@ -53,6 +53,7 @@ export function AuthPage() { onSuccess: async () => { setError(null); await queryClient.invalidateQueries({ queryKey: queryKeys.auth.session }); + await queryClient.invalidateQueries({ queryKey: queryKeys.health }); await queryClient.invalidateQueries({ queryKey: queryKeys.companies.all }); navigate(nextPath, { replace: true }); }, diff --git a/ui/src/pages/InstanceGeneralSettings.tsx b/ui/src/pages/InstanceGeneralSettings.tsx index c25d197069..3737e95c04 100644 --- a/ui/src/pages/InstanceGeneralSettings.tsx +++ b/ui/src/pages/InstanceGeneralSettings.tsx @@ -28,8 +28,9 @@ export function InstanceGeneralSettings() { const signOutMutation = useMutation({ mutationFn: () => authApi.signOut(), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: queryKeys.auth.session }); + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: queryKeys.auth.session }); + await queryClient.invalidateQueries({ queryKey: queryKeys.health }); }, onError: (error) => { setActionError(error instanceof Error ? error.message : "Failed to sign out."); diff --git a/ui/src/pages/InviteLanding.tsx b/ui/src/pages/InviteLanding.tsx index 2c06666f0a..b540fc7528 100644 --- a/ui/src/pages/InviteLanding.tsx +++ b/ui/src/pages/InviteLanding.tsx @@ -374,6 +374,7 @@ export function InviteLandingPage() { setAuthFeedback(null); rememberPendingInviteToken(token); await queryClient.invalidateQueries({ queryKey: queryKeys.auth.session }); + await queryClient.invalidateQueries({ queryKey: queryKeys.health }); await queryClient.invalidateQueries({ queryKey: queryKeys.access.currentBoardAccess }); const { companies: freshCompanies } = await queryClient.fetchQuery(companiesListQueryOptions);