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
- List installed plugins and confirm the id exists before calling login (match against getPluginItem results).
- Re-install/enroll the plugin if the record was removed, then login.
- 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
- Refresh the UI plugin list after any uninstall so stale ids are not reused
- Validate the id against getPluginItem before login
- Treat plugin records as volatile — handle removal between sessions
- Use canonical ids from the record, not user-typed strings
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
- Unsupported plugin file type
- Plugin path is not a file
- Plugin file too large
- Plugin file too large
- Invalid plugin URL
AI-assisted analysis of mihomo-party-org/clash-party@911e090537 (2026-08-30).
Data as JSON: /api/errors/c56a22a4e0f11887.
Report an issue: GitHub.