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

  1. 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).
  2. If the profile is age-encrypted, decrypt or import it through the encrypted-profile flow before editing tunnels.
  3. Hand-edit the profile file so the root is a mapping (key: value pairs at top level).
  4. 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

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


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