{"record":{"id":"5ed10968cb5d53c4","repo":"jlcodes99/cockpit-tools","slug":"transfer-selection-required-5ed109","errorCode":"transfer_selection_required","errorMessage":"transfer_selection_required","messagePattern":"transfer_selection_required","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/scheduledBackupService.ts","lineNumber":353,"sourceCode":"}\n\nexport function isAutoBackupDue(settings: AutoBackupSettings, now = new Date()): boolean {\n  if (!settings.enabled) return false;\n  const selection = getSelectionFromAutoBackupSettings(settings);\n  if (!hasSelection(selection)) return false;\n  const lastBackupAt = normalizeDate(settings.last_backup_at);\n  if (!lastBackupAt) return true;\n  return now.getTime() - lastBackupAt.getTime() >= AUTO_BACKUP_INTERVAL_MS;\n}\n\nexport async function createManagedBackup(params: {\n  trigger: AutoBackupTrigger;\n  selection: DataTransferSelection;\n  retentionDays: number;\n  markAsLastRun?: boolean;\n}): Promise<ManagedBackupResult> {\n  if (!hasSelection(params.selection)) {\n    throw new Error('transfer_selection_required');\n  }\n\n  const executedAt = new Date();\n  const content = await exportDataTransferJson(params.selection);\n  const fileName = buildManagedBackupFileName(params.selection, params.trigger, executedAt);\n  const path = await invoke<string>('write_auto_backup_file', {\n    fileName,\n    content,\n  });\n  const deletedFiles = await cleanupAutoBackupFilesInternal(params.retentionDays);\n\n  if (params.markAsLastRun !== false) {\n    await updateAutoBackupLastRunInternal(executedAt.toISOString());\n  }\n\n  dispatchAutoBackupStateChanged();\n\n  return {","sourceCodeStart":335,"sourceCodeEnd":371,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/src/services/scheduledBackupService.ts#L335-L371","documentation":"createManagedBackup requires a non-empty DataTransferSelection: without at least one platform/account/config selected, exportDataTransferJson would produce an empty or meaningless backup file. hasSelection(params.selection) failed, so the library aborts with transfer_selection_required instead of writing an empty backup.","triggerScenarios":"Calling createManagedBackup (directly or via the auto-backup scheduler) with a selection where all category flags are false / selected sets are empty — e.g. auto-backup fired when the user deselected everything, or code passed a default-initialized empty selection.","commonSituations":"Auto-backup trigger running before the user completed first-run selection setup, a persisted selection reset to empty after an app update, or a UI path allowing 'backup' with nothing checked.","solutions":["Populate params.selection with at least one platform/account/config entry before calling createManagedBackup","Run hasSelection-equivalent validation in the UI and disable the backup action until something is selected","Re-initialize the persisted selection if an app update wiped it","Gate the scheduler so auto-backup is skipped (not thrown) when selection is empty"],"exampleFix":"// before\nawait createManagedBackup({ trigger: 'manual', selection: emptySelection, retentionDays: 30 });\n// after\nif (!hasSelection(emptySelection)) {\n  console.warn('Nothing selected for backup; skipping');\n  return;\n}\nawait createManagedBackup({ trigger: 'manual', selection: emptySelection, retentionDays: 30 });","handlingStrategy":"validation","validationCode":"const hasSelection = (s: DataTransferSelection) =>\n  Boolean(s.platforms?.length || s.accounts?.length || s.includeConfig);\nif (!hasSelection(params.selection)) return; // skip instead of throwing","typeGuard":"function selectionIsNonEmpty(s: DataTransferSelection): boolean {\n  return Object.values(s).some((v) => (Array.isArray(v) ? v.length > 0 : Boolean(v)));\n}","tryCatchPattern":"try {\n  await createManagedBackup(params);\n} catch (e) {\n  if (e.message === 'transfer_selection_required') {\n    openSelectionSetupDialog();\n  } else throw e;\n}","preventionTips":["Validate selection in the UI before enabling the backup button","Have the scheduler skip empty selections rather than erroring","Re-check persisted selections after app updates","Provide first-run defaults so selection is never empty"],"tags":["backup","selection","validation"],"backgroundTag":"missing-required-option","analyzedSha":"1ed8b77992d62ca81fabf744deb0839ad361d5bf","analyzedAt":"2026-09-05T09:51:41.178Z","contentChangedAt":"2026-09-05T09:51:41.178Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}