mihomo-party-org/clash-party · error

Plugin not found

Error message

Plugin not found

What it means

runLogin looks up the plugin record by id via getPluginItem and throws 'Plugin not found' when no record exists for that id. Login (including device-reuse fast path) can only proceed for an already-installed/enrolled plugin. This is an identity/lookup failure, not a network failure.

Source

Thrown at src/main/resolve/plugin/index.ts:148

  return new Error('PLUGIN_LOGIN_FAILED')
}

// 登录(首次登录与重新认证同一入口)。对外抛错经 sanitizeLoginError 脱敏。
export async function loginPlugin(id: string): Promise<void> {
  try {
    await runLogin(id)
  } catch (e) {
    throw sanitizeLoginError(e)
  }
}

// 设备复用仅限「needs-login 且已有 vault」这一种情形:上次 enroll 成功但首份订阅拉取失败留下的
// “孤儿设备”,重拉即可,避免每次重试都 enroll 新设备、消耗服务端设备数上限。
// 其它情形——needs-reauth(显式重新登录)、active(刷新)、无 vault(首装/换机/Linux 无 safeStorage)——
// 一律走全新浏览器登录 + 新设备,与 spec §9「reauth = 再走一次 login 流程、新设备密钥」一致。
async function runLogin(id: string): Promise<void> {
  const record = await getPluginItem(id)
  if (!record) throw new Error('Plugin not found')
  const net = await netOpts(record)

  const existingResult = await readVault(id)
  if (existingResult.kind === 'unavailable') throw new VaultUnavailableError()
  const existing = existingResult.kind === 'ok' ? existingResult.vault : undefined
  if (existing && record.status === 'needs-login') {
    try {
      const content = await fetchWithRediscovery(id, record, existing, net)
      await finishLogin(id, record, content)
      return
    } catch (e) {
      // 孤儿设备已被吊销 → 丢弃旧 vault,落到下面的全新浏览器登录 + 新设备
      if (!(e instanceof GatewayError && e.kind === 'revoked')) throw e
      await removeVault(id)
    }
  }

  // 先确认 Keychain/secret store 可以实际加密,再打开 OAuth 和 enroll,避免用户完成

View on GitHub (pinned to 911e090537)

Solutions

  1. List installed plugins and confirm the id exists before calling login (match against getPluginItem results).
  2. Re-install/enroll the plugin if the record was removed, then login.
  3. Fix the id being passed — trim whitespace, correct casing, use the canonical id from the plugin record.

Example fix

// before
await loginPlugin(staleIdFromUi)
// after
const record = await getPluginItem(id)
if (!record) await installPlugin(descriptor) // re-install before login
await loginPlugin(id)
Defensive patterns

Strategy: try-catch

Validate before calling

const record = await getPluginItem(id)
if (!record) throw new Error(`plugin ${id} is not installed — install before login`)

Type guard

const isInstalled = async (id: string): Promise<boolean> => (await getPluginItem(id)) != null

Try / catch

try {
  await loginPlugin(id)
} catch (e) {
  if (e.message === 'Plugin not found') {
    // refresh plugin list / re-install, then retry once
  } else throw e
}

Prevention

When it happens

Trigger: Calling loginPlugin(id) (or runLogin) with an id that was never installed, was uninstalled, or has a different id spelling — e.g. a stale id held by UI state after the plugin record was removed.

Common situations: Retrying login from a cached list after uninstalling the plugin; typo or casing mismatch in the plugin id; the plugin store was reset/cleared between sessions.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of mihomo-party-org/clash-party@911e090537 (2026-08-30). Data as JSON: /api/errors/c56a22a4e0f11887. Report an issue: GitHub.