{"record":{"id":"b00c9b8d5ece0003","repo":"jlcodes99/cockpit-tools","slug":"app-background-manual-update-check-failed-retry","errorCode":null,"errorMessage":"[App] Background manual update check failed, retrying (${retryIndex}/${totalRetries}) in ${delayMs}ms:","messagePattern":"\\[App\\] Background manual update check failed, retrying \\((.+?)/(.+?)\\) in (.+?)ms:","errorType":"console","errorClass":null,"httpStatus":null,"severity":"info","filePath":"src/App.tsx","lineNumber":2606,"sourceCode":"        } else {\n          // Auto-check only opens the dialog after a real update is found.\n          if (autoInstall && isLinuxManagedUpdate) {\n            writeUpdateLog(\n              'info',\n              `Linux 包管理安装(${updateRuntimeInfo?.linux_install_kind || 'unknown'})跳过静默下载，改为左上角一键安装入口`,\n            );\n          }\n          writeUpdateLog('info', '后台自动更新关闭，先执行无弹窗检查，仅在发现新版本时显示左上角入口');\n          try {\n            const manualCheckStartedAt = performance.now();\n            const update = await retryWithBackoff(\n              async () => runUpdaterCheck(),\n              {\n                delaysMs: UPDATE_CHECK_RETRY_DELAYS_MS,\n                shouldRetry: isRetryableUpdaterError,\n                onRetry: ({ retryIndex, totalRetries, delayMs, error }) => {\n                  const compactError = sanitizeUpdaterErrorMessage(error);\n                  console.warn(\n                    `[App] Background manual update check failed, retrying (${retryIndex}/${totalRetries}) in ${delayMs}ms:`,\n                    error,\n                  );\n                  writeUpdateLog(\n                    'warn',\n                    `后台手动更新检查失败，准备重试(${retryIndex}/${totalRetries})，delay=${delayMs}ms，error=${compactError}`,\n                  );\n                },\n              },\n            );\n            console.log(\n              `[StartupPerf][UpdateCheck] manual runUpdaterCheck completed in ${(performance.now() - manualCheckStartedAt).toFixed(2)}ms; hasUpdate=${Boolean(update)}`,\n            );\n\n            if (update) {\n              if (skippedVersion && update.version === skippedVersion) {\n                console.log('[App] Update skipped by user, ignoring:', update.version);\n                writeUpdateLog('info', `检测到新版本但已跳过: version=${update.version}`);","sourceCodeStart":2588,"sourceCodeEnd":2624,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/src/App.tsx#L2588-L2624","documentation":"Same retry machinery as the silent check, but for a user-triggered manual update check running in the background. Each retryable failure of runUpdaterCheck logs this warning and schedules the next attempt; the user-facing update log also records a localized warning with the sanitized error. Final failure only matters after all retries in UPDATE_CHECK_RETRY_DELAYS_MS are consumed.","triggerScenarios":"runUpdaterCheck() rejects with isRetryableUpdaterError === true (network timeout, DNS, HTTP 5xx, proxy errors) while a manual check triggered from the UI runs in the background.","commonSituations":"User clicks 'check for updates' while offline or behind a restrictive proxy; update CDN outage; VPN dropping mid-check.","solutions":["If a later retry succeeds, no action needed — verify via the update log's final entry","Check connectivity/proxy to the update endpoint; test the manifest URL in a browser on the same machine","Review compactError in the update log to classify the failure (timeout vs HTTP status vs TLS)","If retries keep failing, show the user a clear error state with a retry button instead of only logging"],"exampleFix":"// before\nonRetry: ({ retryIndex, totalRetries, delayMs, error }) => {\n  console.warn(`[App] Background manual update check failed, retrying (${retryIndex}/${totalRetries})...`, error);\n}\n// after\nonRetry: ({ retryIndex, totalRetries, delayMs, error }) => {\n  console.warn(`[App] Background manual update check failed, retrying (${retryIndex}/${totalRetries})...`, sanitizeUpdaterErrorMessage(error));\n  setUpdateCheckStatus('retrying', retryIndex, totalRetries); // keep UI informed\n}","handlingStrategy":"retry","validationCode":"if (typeof navigator !== 'undefined' && navigator.onLine === false) {\n  setUpdateCheckStatus('offline');\n  return;\n}","typeGuard":"function isRetryableUpdaterError(e: unknown): boolean {\n  const msg = e instanceof Error ? e.message : String(e);\n  return /timeout|network|ECONN|5\\d\\d|temporarily/i.test(msg);\n}","tryCatchPattern":"try {\n  await retryWithDelay(() => runUpdaterCheck(), {\n    delaysMs: UPDATE_CHECK_RETRY_DELAYS_MS,\n    shouldRetry: isRetryableUpdaterError,\n    onRetry: ({ retryIndex, totalRetries, delayMs, error }) => {\n      console.warn(`[App] Background manual update check failed, retrying (${retryIndex}/${totalRetries}) in ${delayMs}ms:`, error);\n      writeUpdateLog('warn', `后台手动更新检查失败，准备重试(${retryIndex}/${totalRetries})，error=${sanitizeUpdaterErrorMessage(error)}`);\n    },\n  });\n} catch (finalError) {\n  setUpdateCheckStatus('failed', sanitizeUpdaterErrorMessage(finalError));\n}","preventionTips":["Reflect retry progress in the UI so a manual check doesn't look hung","Fail fast on non-retryable errors (auth, 4xx)","Log the sanitized error for both console and update log","Offer a manual retry button after retries are exhausted"],"tags":["updater","network","retry","manual-check"],"backgroundTag":"update-check-failed","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"}