diff --git a/packages/adapter-utils/src/sandbox-managed-runtime.test.ts b/packages/adapter-utils/src/sandbox-managed-runtime.test.ts index d9331d310d2..9b204c62dc5 100644 --- a/packages/adapter-utils/src/sandbox-managed-runtime.test.ts +++ b/packages/adapter-utils/src/sandbox-managed-runtime.test.ts @@ -444,7 +444,7 @@ describe("sandbox managed runtime", () => { await prepared.restoreWorkspace(); expect(await readFile(path.join(local, "file.txt"), "utf8")).toBe("after"); } finally { capture.mockRestore(); } - }); + }, 30_000); it.each(["host_current", "adopt_remote", "durable_seed"] as const)("stages and restores both project repositories with independent Git histories (%s)", async (mode) => { const root = await mkdtemp(path.join(os.tmpdir(), "paperclip-multi-repo-")); diff --git a/ui/src/components/OnboardingWizard.test.tsx b/ui/src/components/OnboardingWizard.test.tsx index e7f2d74d928..cc5ecbc1637 100644 --- a/ui/src/components/OnboardingWizard.test.tsx +++ b/ui/src/components/OnboardingWizard.test.tsx @@ -3485,6 +3485,36 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", ( } finally { await act(async () => root.unmount()); } }); + it("unlocks Claude setup when automatic runner availability changes during verification", async () => { + mockAdapterBuild.buildAdapterConfig.mockImplementation(values => buildClaudeLocalConfig(values!)); + mockAdaptersApi.list.mockResolvedValue([setupAdapterInfo({ type: "claude_local", defaultRunner: "paperclip" })]); + const finishes: Array<(value: typeof passed) => void> = []; + mockAgentsApi.testEnvironment.mockImplementation(() => new Promise(resolve => finishes.push(resolve))); + const { root, queryClient } = await openStep4({ adapterType: "claude_local" }); + try { + await pickSource(/Claude/); + await detected(); + expect(mockAgentsApi.testEnvironment).toHaveBeenCalledTimes(1); + expectTesting(); + mockAdaptersApi.list.mockResolvedValue([setupAdapterInfo({ type: "claude_local", defaultRunner: "legacy" })]); + await act(async () => queryClient.setQueryData(queryKeys.adapters.availability("company-new", LOCAL_ENVIRONMENT.id), [setupAdapterInfo({ type: "claude_local", defaultRunner: "legacy" })])); + await settle(); + expect(document.body.textContent).toContain("Configuration changed during setup"); + expect(button("Connect").disabled).toBe(false); + expect(mockAgentsApi.testEnvironment).toHaveBeenCalledTimes(1); + await act(async () => finishes[0](passed)); + await settle(); + expect(mockAgentsApi.hire).not.toHaveBeenCalled(); + await click("Connect"); + expect(mockAgentsApi.testEnvironment).toHaveBeenCalledTimes(2); + await act(async () => finishes[1](passed)); + await settle(); + expect(mockAgentsApi.hire).toHaveBeenCalledTimes(1); + const hired = mockAgentsApi.hire.mock.calls[0] as unknown as [string, { adapterConfig: Record }]; + expect(hired[1].adapterConfig.maxTurnsPerRun).toBe(1000); + } finally { await act(async () => root.unmount()); } + }); + it("fails closed on Claude discovery rejection and retries availability before verification", async () => { mockAdaptersApi.list.mockRejectedValue(new Error("Discovery unavailable")); const { root, queryClient } = await openStep4({ adapterType: "claude_local" }); diff --git a/ui/src/components/OnboardingWizard.tsx b/ui/src/components/OnboardingWizard.tsx index 522c9ad2ec6..4abfb9ad743 100644 --- a/ui/src/components/OnboardingWizard.tsx +++ b/ui/src/components/OnboardingWizard.tsx @@ -1032,7 +1032,7 @@ function OnboardingWizardInner({ null, [loginEnvironmentList, resolvedLoginEnvironmentId], ); - const claudeSetupSelectionKey = JSON.stringify([createdCompanyId, adapterType, runner, model, command, + const claudeSetupSelectionKey = JSON.stringify([createdCompanyId, adapterType, runner, resolvedRunner, model, command, args, url, credentialMode, apiKey, selectedSavedKey, selectedApiKey?.binding, savedSubscription?.binding, resolvedLoginEnvironmentId, resolvedLoginEnvironment?.config]); const claudeSetupSelectionRef = useRef(claudeSetupSelectionKey);