From be407f34567d57ca71066ef2ca085db3cee67a44 Mon Sep 17 00:00:00 2001 From: Tonio Date: Sat, 5 Sep 2026 10:08:28 -0700 Subject: [PATCH] fix(onboarding): hide the probe's diagnostics while the hire is in flight (#12902) After a successful sign-in, a block of amber diagnostics flashed up and vanished as the step advanced. They are the identity and target INFO checks every environment test reports: they make the result a warn without blocking anything, and the step rendered any non-pass result, so they owned the screen for the window between the probe returning and setStep(5). Gated on loading rather than the connect phase. A blocking result stops the hire and handleGiveHeartbeat clears loading in the finally after its early return, so a genuine block still shows its checks while a hire in flight shows none. The button needed no change: step 4 forces the footer's loading to false and takes its label from connectCta, which reads Connecting for that whole window. --- ui/src/components/OnboardingWizard.test.tsx | 60 +++++++++++++++++++++ ui/src/components/OnboardingWizard.tsx | 15 +++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/ui/src/components/OnboardingWizard.test.tsx b/ui/src/components/OnboardingWizard.test.tsx index 9640bcf87f..562b14318f 100644 --- a/ui/src/components/OnboardingWizard.test.tsx +++ b/ui/src/components/OnboardingWizard.test.tsx @@ -2337,6 +2337,66 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", ( await act(async () => root.unmount()); }); + it("shows no probe diagnostics between the sign-in succeeding and the step advancing", async () => { + // Reported from staging: a block of amber diagnostics flashed up right + // after a successful sign-in. They are the identity and target INFO + // checks every run reports, which make the result a `warn` without + // blocking anything — so they rendered for the window between the probe + // returning and the step advancing, reading as an error thrown by the + // sign-in that had just succeeded. + mockAgentsApi.getAdapterAuthSignal.mockResolvedValue({ status: "absent" }); + mockAgentsApi.getClaudeSetupTokenLoginStatus.mockResolvedValue({ + sessionId: "claude-session-1", + status: "authenticated", + expiresAt: new Date(Date.now() + 600_000).toISOString(), + }); + mockAgentsApi.testEnvironment.mockResolvedValue({ + adapterType: "claude_local", + status: "warn", + checks: [ + { + code: "environment_identity", + level: "info", + message: 'Environment test identity for "Paperclip Computer".', + detail: "paperclipLeaseId=ff9e58e9; provider=daytona", + }, + ], + testedAt: new Date().toISOString(), + }); + // Hold the hire open, which is the window the diagnostics appeared in: + // the probe has returned but `loading` is not cleared until the `finally` + // that runs after the step advances. + let finishHire: (v: { agent: { id: string }; approval: null }) => void = () => {}; + mockAgentsApi.hire.mockReturnValue( + new Promise((resolve) => { + finishHire = resolve; + }), + ); + + const { root } = await openStep4({ adapterType: "claude_local" }); + await pickSource(/Claude/); + for (let i = 0; i < 8; i++) await flushReact(); + + // Past the deliberate hold, so the hire is running and its probe is done. + await act(async () => { + await new Promise((resolve) => window.setTimeout(resolve, CONNECTED_HOLD_MS + 400)); + }); + for (let i = 0; i < 8; i++) await flushReact(); + + expect(mockAgentsApi.hire).toHaveBeenCalled(); + expect(document.body.textContent).not.toContain("Environment test identity"); + expect(document.body.textContent).not.toContain("Warnings"); + // And the button goes on saying what is happening rather than going quiet. + expect( + [...document.body.querySelectorAll("button")].pop()?.textContent?.trim(), + ).toBe("Connecting"); + + await act(async () => finishHire({ agent: { id: "agent-1" }, approval: null })); + for (let i = 0; i < 6; i++) await flushReact(); + + await act(async () => root.unmount()); + }); + it("starts no login when Back interrupts the collapse", async () => { // Backing out before the card has opened has nothing to close. Unwinding // through the card beat regardless mounted the panel — which starts a diff --git a/ui/src/components/OnboardingWizard.tsx b/ui/src/components/OnboardingWizard.tsx index 91ac467f53..b976feb495 100644 --- a/ui/src/components/OnboardingWizard.tsx +++ b/ui/src/components/OnboardingWizard.tsx @@ -3232,7 +3232,20 @@ function OnboardingWizardInner({ step, so this block renders only when a probe has actually found something: the checks the blocking error tells the customer to fix have to be visible somewhere. */} - {isLocalAdapter && (adapterEnvError || (adapterEnvResult && adapterEnvResult.status !== "pass")) && ( + {/* Not while the hire is in flight. The probe's result lands + before the hire it gates has finished, so a warn that does + not block — the identity and target INFO checks, which + every run reports — rendered a block of diagnostics for the + moment between the probe returning and the step advancing. + It read as an error thrown up by a sign-in that had just + succeeded. + + `loading` is the right gate rather than the connect phase: + it is false again by the time a blocking result has stopped + the hire, because `handleGiveHeartbeat` clears it in its + `finally` after the early return — so a genuine block still + shows its checks, which is the whole reason this is here. */} + {isLocalAdapter && !loading && (adapterEnvError || (adapterEnvResult && adapterEnvResult.status !== "pass")) && (
{adapterEnvError && (