{"record":{"id":"88c153351aa8896b","repo":"halo-dev/halo","slug":"file-is-required","errorCode":null,"errorMessage":"File is required","messagePattern":"File is required","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ui/console-src/modules/interface/themes/components/list-tabs/LocalUpload.vue","lineNumber":57,"sourceCode":"  if (body?.type === THEME_ALREADY_EXISTS_TYPE) {\n    handleCatchExistsException(body, file?.data as File | undefined);\n  }\n};\n\nconst handleCatchExistsException = async (\n  error: ThemeInstallationErrorResponse,\n  file?: File\n) => {\n  Dialog.info({\n    title: t(\"core.theme.operations.existed_during_installation.title\"),\n    description: t(\n      \"core.theme.operations.existed_during_installation.description\"\n    ),\n    confirmText: t(\"core.common.buttons.confirm\"),\n    cancelText: t(\"core.common.buttons.cancel\"),\n    onConfirm: async () => {\n      if (!file) {\n        throw new Error(\"File is required\");\n      }\n\n      await consoleApiClient.theme.theme.upgradeTheme({\n        name: error.themeName,\n        file: file,\n      });\n\n      Toast.success(t(\"core.common.toast.upgrade_success\"));\n\n      queryClient.invalidateQueries({ queryKey: [\"themes\"] });\n      themeStore.fetchActivatedTheme();\n\n      activeTabId.value = \"installed\";\n    },\n  });\n};\n</script>\n","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/ui/console-src/modules/interface/themes/components/list-tabs/LocalUpload.vue#L39-L75","documentation":"Thrown inside the onConfirm callback of a Dialog in LocalUpload.vue's theme-install 'already exists' flow. When a theme ZIP is uploaded but the theme already exists, the backend returns THEME_ALREADY_EXISTS_TYPE; the handler asks the user to confirm an upgrade and needs the original File to call consoleApiClient.theme.theme.upgradeTheme. If file is undefined (the Uppy upload's file.data was not retained), the upgrade cannot proceed and this throws.","triggerScenarios":"Uppy's onError fires with a body.type matching the already-exists error, but file?.data (the original File object) is undefined — e.g. the file reference was garbage-collected, the upload error occurred before file.data was attached, or the Uppy config strips file data after completion. The user confirms the upgrade dialog and onConfirm throws 'File is required'.","commonSituations":"Theme already installed and user tries to re-install via the local upload tab; Uppy version change altered the file lifecycle so file.data is gone by the time onError/onConfirm runs; a large theme file was cleared from memory; custom Uppy plugin that doesn't retain the File object in the error payload.","solutions":["Retain the uploaded File reference: capture it during Uppy's file-added/upload-success events into a ref, and pass that into handleCatchExistsException instead of relying on onError's file?.data.","Before showing the upgrade dialog, verify file is a File instance; if not, re-prompt the user to re-select the theme ZIP.","Confirm the Uppy configuration preserves file.data through to the onError callback (check the @halo-dev upload wrapper version).","Catch the throw in onConfirm so the Dialog doesn't surface an unhandled rejection; show a Toast asking the user to re-upload."],"exampleFix":"// before\nonConfirm: async () => {\n  if (!file) {\n    throw new Error(\"File is required\");\n  }\n  await consoleApiClient.theme.theme.upgradeTheme({ name: error.themeName, file });\n// after — guard with user-facing feedback instead of throwing inside the dialog callback\nonConfirm: async () => {\n  if (!(file instanceof File)) {\n    Toast.error(t(\"core.theme.operations.existed_during_installation.reselect\"));\n    return;\n  }\n  await consoleApiClient.theme.theme.upgradeTheme({ name: error.themeName, file });","handlingStrategy":"type-guard","validationCode":"// Retain the File and verify it before the upgrade call\nconst uploadedFile = ref<File | null>(null);\n// on Uppy 'file-added': uploadedFile.value = file.data;\nfunction canUpgradeTheme(f: File | null | undefined): f is File {\n  return f instanceof File;\n}\n// in onConfirm: if (!canUpgradeTheme(uploadedFile.value)) { reselectPrompt(); return; }","typeGuard":"function isFile(f: unknown): f is File {\n  return typeof File !== \"undefined\" && f instanceof File;\n}","tryCatchPattern":"onConfirm: async () => {\n  try {\n    if (!isFile(file)) throw new Error(\"File is required\");\n    await consoleApiClient.theme.theme.upgradeTheme({ name: error.themeName, file });\n  } catch (e) {\n    Toast.error(e instanceof Error ? e.message : \"Upgrade failed\");\n  }\n}","preventionTips":["Capture the uploaded File in a ref during Uppy's file-added event rather than relying on onError's file.data lifecycle.","Confirm the Uppy wrapper preserves file.data through to onError/onConfirm after version upgrades.","Re-prompt the user to re-select the ZIP if the File reference is gone by confirm time."],"tags":["themes","uppy","vue","dialog","validation","upgrade"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}