{"record":{"id":"3182de464dcd90fd","repo":"mihomo-party-org/clash-party","slug":"plugin-vault-is-temporarily-unavailable","errorCode":null,"errorMessage":"Plugin vault is temporarily unavailable","messagePattern":"Plugin vault is temporarily unavailable","errorType":"exception","errorClass":"VaultUnavailableError","httpStatus":null,"severity":"error","filePath":"src/main/resolve/plugin/index.ts","lineNumber":152,"sourceCode":"export async function loginPlugin(id: string): Promise<void> {\n  try {\n    await runLogin(id)\n  } catch (e) {\n    throw sanitizeLoginError(e)\n  }\n}\n\n// 设备复用仅限「needs-login 且已有 vault」这一种情形：上次 enroll 成功但首份订阅拉取失败留下的\n// “孤儿设备”，重拉即可，避免每次重试都 enroll 新设备、消耗服务端设备数上限。\n// 其它情形——needs-reauth（显式重新登录）、active（刷新）、无 vault（首装/换机/Linux 无 safeStorage）——\n// 一律走全新浏览器登录 + 新设备，与 spec §9「reauth = 再走一次 login 流程、新设备密钥」一致。\nasync function runLogin(id: string): Promise<void> {\n  const record = await getPluginItem(id)\n  if (!record) throw new Error('Plugin not found')\n  const net = await netOpts(record)\n\n  const existingResult = await readVault(id)\n  if (existingResult.kind === 'unavailable') throw new VaultUnavailableError()\n  const existing = existingResult.kind === 'ok' ? existingResult.vault : undefined\n  if (existing && record.status === 'needs-login') {\n    try {\n      const content = await fetchWithRediscovery(id, record, existing, net)\n      await finishLogin(id, record, content)\n      return\n    } catch (e) {\n      // 孤儿设备已被吊销 → 丢弃旧 vault，落到下面的全新浏览器登录 + 新设备\n      if (!(e instanceof GatewayError && e.kind === 'revoked')) throw e\n      await removeVault(id)\n    }\n  }\n\n  // 先确认 Keychain/secret store 可以实际加密，再打开 OAuth 和 enroll，避免用户完成\n  // 浏览器登录后才发现私钥无法持久化。旧 Electron 兼容包会在这里走同步探测。\n  await ensureVaultWritable()\n\n  const wk = await discoverGateway(record.loginUrl, net)","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/mihomo-party-org/clash-party/blob/911e090537acdf7c50bee1c3aebecc2ef119a8b5/src/main/resolve/plugin/index.ts#L134-L170","documentation":"runLogin reads the plugin's credential vault before deciding whether to reuse an existing device. If readVault returns kind 'unavailable' (e.g. the OS keychain/safeStorage backend cannot be accessed), VaultUnavailableError with message 'Plugin vault is temporarily unavailable' is thrown. Login is intentionally aborted rather than proceeding without credentials.","triggerScenarios":"Calling loginPlugin when the system credential store is locked or unreachable — Linux without a working safeStorage/keyring backend, keychain locked after reboot, or the vault service temporarily down.","commonSituations":"Running on Linux in a headless/CI session with no secret-service (gnome-keyring/kwallet) available; macOS Keychain access denied; keychain still locked at login time.","solutions":["Ensure a keyring/secret service is running (on Linux install/start gnome-keyring or kwallet) and unlock it, then retry.","Retry after the keychain is unlocked — the error is explicitly 'temporarily unavailable'.","Verify Electron safeStorage is usable in the current environment before initiating login.","If the platform genuinely has no safeStorage, expect the full browser-login path (spec §9) rather than vault reuse."],"exampleFix":"// before\nawait loginPlugin(id) // fails while keychain is locked\n// after\ntry {\n  await loginPlugin(id)\n} catch (e) {\n  if (e instanceof VaultUnavailableError) {\n    await promptUnlockKeychain()\n    await loginPlugin(id) // retry once unlocked\n  } else throw e\n}","handlingStrategy":"retry","validationCode":"// probe vault availability before login\nconst probe = await readVault(id)\nif (probe.kind === 'unavailable') throw new Error('unlock the system keychain before logging in')","typeGuard":null,"tryCatchPattern":"try {\n  await loginPlugin(id)\n} catch (e) {\n  if (e instanceof VaultUnavailableError) {\n    await promptUnlockKeychain()\n    await loginPlugin(id) // bounded retry after unlock\n  } else throw e\n}","preventionTips":["Ensure a secret service (gnome-keyring/kwallet) is installed and running on Linux","Unlock the OS keychain before invoking login flows","Detect headless/CI environments where safeStorage is unavailable and skip vault reuse","Surface 'temporarily unavailable' to users with an unlock-and-retry action"],"tags":["vault","keychain","storage","transient"],"backgroundTag":"keychain-unavailable","analyzedSha":"911e090537acdf7c50bee1c3aebecc2ef119a8b5","analyzedAt":"2026-08-30T13:00:49.174Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}