mihomo-party-org/clash-party · error
Profile "${id}" contains HTML instead of YAML. The subscript
Error message
Profile "${id}" contains HTML instead of YAML. The subscription may have returned an error page. Please re-import or update the subscription. What it means
parseProfileContent detects when a stored profile's content is actually HTML (DOCTYPE, <html> tags, or inline <style> in the first 500 chars) rather than YAML. This indicates the subscription URL returned an error page (login, captcha, 4xx/5xx HTML) that got saved as the profile, so parsing is aborted with a descriptive message telling the user to re-import.
Source
Thrown at src/main/config/profile.ts:620
return await parseProfileContent(id, await getProfileStr(id), item?.ageSecretKey)
}
export async function parseProfileContent(
id: string | undefined,
content: string,
ageSecretKey?: string
): Promise<IMihomoConfig> {
const profile = await decryptAgeContent(content, ageSecretKey, `profile "${id || 'default'}"`)
// 检测是否为 HTML 内容(订阅返回错误页面)
const trimmed = profile.trim()
if (
trimmed.startsWith('<!DOCTYPE') ||
trimmed.startsWith('<html') ||
trimmed.startsWith('<HTML') ||
/<style[^>]*>/i.test(trimmed.slice(0, 500))
) {
throw new Error(
`Profile "${id}" contains HTML instead of YAML. The subscription may have returned an error page. Please re-import or update the subscription.`
)
}
try {
let result = parse(profile)
if (typeof result !== 'object') result = {}
return result as IMihomoConfig
} catch (e) {
const msg = e instanceof Error ? e.message : String(e)
throw new Error(`Failed to parse profile "${id}": ${msg}`)
}
}
// attachment;filename=xxx.yaml; filename*=UTF-8''%xx%xx%xx
function parseFilename(str: string): string {
if (str.match(/filename\*=.*''/)) {
const parts = str.split(/filename\*=.*''/)View on GitHub (pinned to 911e090537)
Solutions
- Re-fetch the subscription: open the URL and confirm it returns YAML, then re-import or update the profile.
- Check subscription authentication (token/cookie) — renew the expired link from the provider.
- If behind a captive portal, complete portal login and retry the import.
- Delete the corrupted profile entry and add it again from the correct URL.
Example fix
// before await getProfile(id) // stored content is '<!DOCTYPE html>...' // after await fetchSub(correctUrl, options) // re-fetch valid YAML, then load the profile
Defensive patterns
Strategy: try-catch
Validate before calling
const trimmed = content.trim().slice(0, 500)
const looksHtml = /^<!DOCTYPE|^<html/i.test(trimmed) || /<style[^>]*>/i.test(trimmed)
if (looksHtml) {
// re-fetch the subscription before calling getProfile
await reImportSubscription(profileUrl)
} Type guard
function looksLikeHtml(content: string): boolean {
const t = content.trim()
return t.startsWith('<!DOCTYPE') || /^<html/i.test(t) || /<style[^>]*>/i.test(t.slice(0, 500))
} Try / catch
try {
const cfg = await getProfile(id)
} catch (e) {
if (e.message.includes('contains HTML instead of YAML')) {
// subscription returned an error page: renew token and re-import
}
} Prevention
- Validate subscription responses are YAML at import time, not just at read time.
- Detect HTML/captcha/login pages when fetching subscriptions and fail fast.
- Renew subscription tokens before they expire.
When it happens
Trigger: baseProfile or getProfile reads a profile whose content starts with '<!DOCTYPE', '<html', '<HTML', or contains a <style> tag within the first 500 characters.
Common situations: Subscription token expired and provider served a login page; captive portal or ISP interception page saved during import; provider rate-limited with an HTML error page; user pasted an HTML URL by mistake.
Related errors
- Subscription failed: Profile is not a valid YAML
- Subscription failed: Profile missing proxies or providers
- Failed to parse profile "${id}": ${msg}
- Empty URL
- ${i18next.t('mihomo.error.profileCheckFailed')}: ${allLines.
AI-assisted analysis of mihomo-party-org/clash-party@911e090537 (2026-08-30).
Data as JSON: /api/errors/49f3637ee1ca10e1.
Report an issue: GitHub.