mihomo-party-org/clash-party · error · Error
Profile is not a YAML mapping
Error message
Profile is not a YAML mapping
What it means
EditTunnelsModal's loadContent() fetches the profile text via getProfileStr(id), parses it with YAML load(), and throws 'Profile is not a YAML mapping' when the parsed root is not a plain object (e.g. an array, scalar, date, or age-encrypted ciphertext). The guard exists because the old code silently coerced non-mapping roots to {}, and saving would then overwrite the whole subscription with an empty profile.
Source
Thrown at src/renderer/src/components/profiles/edit-tunnels-modal.tsx:223
const targetInvalid = useMemo(() => {
if (!newTunnel.target.trim()) return false
return !isValidTarget(newTunnel.target)
}, [newTunnel.target])
const canSubmit = useMemo(() => isTunnelValid(newTunnel), [newTunnel])
useEffect(() => {
const loadContent = async (): Promise<void> => {
setIsLoading(true)
setLoadFailed(false)
try {
const content = await getProfileStr(id)
const parsed = load(content)
// 解析结果不是 YAML mapping 时(例如 age 密文、根数组或日期),
// 旧实现静默退化成 {} 且不提示,保存就会把整份订阅写没。
if (Object.prototype.toString.call(parsed) !== '[object Object]') {
throw new Error('Profile is not a YAML mapping')
}
const nextProfile = parsed as ProfileYaml
setProfile(nextProfile)
setTunnels(parseTunnels(nextProfile.tunnels))
setProxyNames(collectProxyNames(nextProfile))
} catch (e) {
setLoadFailed(true)
toast.error(
t('profiles.editTunnels.loadError') + ': ' + (e instanceof Error ? e.message : String(e))
)
} finally {
setIsLoading(false)
}
}
loadContent()
}, [id, t])
View on GitHub (pinned to 911e090537)
Solutions
- Fix the profile source: re-download the subscription or point it at a URL returning a proper YAML mapping (top-level keys like proxies/proxy-groups).
- If the profile is age-encrypted, decrypt or import it through the encrypted-profile flow before editing tunnels.
- Hand-edit the profile file so the root is a mapping (key: value pairs at top level).
- Keep the thrown error visible to the user and abort saving (the current catch already prevents overwriting the subscription).
Example fix
// before (old silent behavior)
const nextProfile = (Object.prototype.toString.call(parsed) === '[object Object]' ? parsed : {}) as ProfileYaml
// after
if (Object.prototype.toString.call(parsed) !== '[object Object]') {
throw new Error('Profile is not a YAML mapping')
}
const nextProfile = parsed as ProfileYaml Defensive patterns
Strategy: validation
Validate before calling
import { load } from 'yaml'
const parsed = load(content)
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed) || parsed instanceof Date) {
throw new Error('Profile is not a YAML mapping')
} Type guard
function isYamlMapping(v: unknown): v is Record<string, unknown> {
return v !== null && typeof v === 'object' && !Array.isArray(v) && !(v instanceof Date)
} Try / catch
try {
const content = await getProfileStr(id)
const parsed = load(content)
if (!isYamlMapping(parsed)) throw new Error('Profile is not a YAML mapping')
} catch (e) {
if (e.message === 'Profile is not a YAML mapping') {
// show UI message; never write back to this profile
} else {
throw e
}
} Prevention
- Never silently default a parsed profile to {} before saving.
- Validate subscription URLs return YAML mappings (root-level keys) after download.
- Detect age-encrypted/undecryptable content before opening editors that save back.
- Back up the original profile file before any editor writes to it.
When it happens
Trigger: Opening the tunnels editor for a profile whose stored content is an age-encrypted blob, a bare YAML list at the root, a scalar, or a date — anything whose Object.prototype.toString class is not '[object Object]'.
Common situations: Subscribing to a URL that returns encrypted (age) content the app cannot decrypt; a hand-edited profile file starting with '- ' (list) or a plain string; a subscription endpoint returning an error page that parses as a scalar YAML.
Related errors
- Subscription failed: Profile missing proxies or providers
- Invalid latest.yml from update source
- Subscription failed: Profile is not a valid YAML
- Empty URL
- Profile "${id}" contains HTML instead of YAML. The subscript
AI-assisted analysis of mihomo-party-org/clash-party@911e090537 (2026-08-30).
Data as JSON: /api/errors/4079664528372bce.
Report an issue: GitHub.