{"record":{"id":"ad8a9a84cde1d5db","repo":"amruthpillai/reactive-resume","slug":"loading-ai-providers-please-try-again-in-a-moment","errorCode":null,"errorMessage":"Loading AI providers. Please try again in a moment.","messagePattern":"Loading AI providers\\. Please try again in a moment\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/web/src/dialogs/resume/import.tsx","lineNumber":165,"sourceCode":"\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tlet data: ResumeData | undefined;\n\n\t\t\t\tif (value.type === \"json-resume-json\") {\n\t\t\t\t\tdata = parseJSONResume(await value.file.text());\n\t\t\t\t}\n\n\t\t\t\tif (value.type === \"reactive-resume-json\") {\n\t\t\t\t\tdata = parseReactiveResumeJSON(await value.file.text());\n\t\t\t\t}\n\n\t\t\t\tif (value.type === \"reactive-resume-v4-json\") {\n\t\t\t\t\tdata = parseReactiveResumeV4JSON(await value.file.text());\n\t\t\t\t}\n\n\t\t\t\tif (value.type === \"pdf\") {\n\t\t\t\t\tif (isLoadingAiProviders) throw new Error(t`Loading AI providers. Please try again in a moment.`);\n\t\t\t\t\tif (!hasUsableProvider)\n\t\t\t\t\t\tthrow new Error(t`This feature requires a connected AI provider. Please set one up in the settings.`);\n\n\t\t\t\t\tconst base64 = await fileToBase64(value.file);\n\n\t\t\t\t\tdata = await client.ai.parsePdf({\n\t\t\t\t\t\tfile: { name: value.file.name, data: base64 },\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tif (value.type === \"docx\") {\n\t\t\t\t\tif (isLoadingAiProviders) throw new Error(t`Loading AI providers. Please try again in a moment.`);\n\t\t\t\t\tif (!hasUsableProvider)\n\t\t\t\t\t\tthrow new Error(t`This feature requires a connected AI provider. Please set one up in the settings.`);\n\n\t\t\t\t\tconst base64 = await fileToBase64(value.file);\n\n\t\t\t\t\tconst mediaType =","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/amruthpillai/reactive-resume/blob/3a5b12e2a40374a9571988701fcb75c5a1831c42/apps/web/src/dialogs/resume/import.tsx#L147-L183","documentation":"Thrown synchronously inside the PDF import branch of the resume import dialog when useHasUsableAiProvider()'s isLoading flag is still true. The hook wraps orpc.aiProviders.list (a TanStack Query); isLoading is true before the first response arrives. This guard prevents calling client.ai.parsePdf before the provider list is known, because parsePdf needs a usable provider to exist. The thrown Error propagates to the onSubmit catch block which renders it as a toast.","triggerScenarios":"User opens the import dialog, selects a PDF, and clicks Import during the brief window before the aiProviders.list query resolves on mount. Also possible if the query is stale/refetching after a network change and isLoading flips back. The submit button is supposed to be disabled when aiRequired && !hasUsableProvider, but that disable logic does not account for the loading state, so a fast click can slip through.","commonSituations":"Slow network or cold cache so the provider list query takes longer than the user's click. The query was unmounted/remounted (dialog re-opened) forcing a fresh fetch. React Query default staleTime causing a refetch on focus that briefly sets isLoading/isFetching. Race between the file-read and the provider-list fetch where the file finishes first.","solutions":["Wait a moment and retry the import — this is a transient race that resolves once aiProviders.list settles.","To prevent recurrence, also disable the Import button while providers are loading: add isLoadingAiProviders to the submit button's disabled condition (apps/web/src/dialogs/resume/import.tsx:428).","If it persists, check the Network tab for the aiProviders.list request — a failing/CORS-blocked request leaves the query stuck loading; fix the backend/CORS config.","Ensure the React Query client isn't being recreated on each render, which would reset query cache and reload providers every time."],"exampleFix":"// before: submit disabled only when a provider is known-missing, not while loading\n<Button type=\"submit\" disabled={!type || !file || isImporting || (aiRequired && !hasUsableProvider)}>\n\n// after: also block submit while AI providers are still loading\n<Button\n  type=\"submit\"\n  disabled={\n    !type || !file || isImporting || (aiRequired && (isLoadingAiProviders || !hasUsableProvider))\n  }\n>","handlingStrategy":"validation","validationCode":"const { hasUsableProvider, isLoading } = useHasUsableAiProvider();\nfunction canImportPdf(): boolean {\n  return !isLoading && hasUsableProvider;\n}\n// gate the submit: only call client.ai.parsePdf when canImportPdf() is true","typeGuard":"function aiProvidersReady(isLoading: boolean, hasUsable: boolean): boolean {\n  return !isLoading && hasUsable;\n}","tryCatchPattern":"// onSubmit catch already toasts the message; to handle gracefully, pre-check:\nif (isLoadingAiProviders) {\n  toast.info('Loading AI providers, please wait…');\n  return;\n}","preventionTips":["Disable the Import button while isLoadingAiProviders is true (add it to the disabled prop).","Prefetch orpc.aiProviders.list on the parent route so it is warm when the dialog opens.","Do not recreate the React Query client per render."],"tags":["ai-provider","import","race-condition","react-query","ui"],"backgroundTag":null,"analyzedSha":"3a5b12e2a40374a9571988701fcb75c5a1831c42","analyzedAt":"2026-08-12T22:31:22.666Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}