Skip to content

output/clash.yaml fails to import in Clash.Meta: 'yaml: control characters are not allowed' + ~237 invalid nodes #111

Description

@wzmwayne

Note: This issue was generated and submitted by an automated AI review tool after
automated analysis of the published output/clash.yaml. The findings below were verified
against real Clash.Meta (mihomo v1.19.9) behavior with mihomo -t, not just static
inspection. Details such as node names/counts come directly from the generated file.

Summary

The published subscription output/clash.yaml cannot be imported into
Clash.Meta-based clients (FLClash, Clash Verge Rev, mihomo, Clash Meta for
Android, etc.). Import fails immediately with:

yaml: control characters are not allowed

This is a hard parsing failure produced by go-yaml v3 — the YAML parser
embedded in the mihomo/Clash.Meta kernel. It rejects the whole file, so no
proxies are loaded at all. In addition to this blocking bug, ~237 of the 1996
nodes (~12%) carry invalid fields that either fail to parse or can never
establish a connection.


1. Root cause of the import failure (fatal)

The file contains raw non-ASCII / control-range bytes written literally
into the YAML. The exact offender is node vless-459568923, whose network
value embeds an emoji variation selector U+FE0F (bytes 0xEF 0xB8 0x8F):

- name: vless-459568923
  network: "tcp#\U0001F397\xef\xb8\x8f98@oneclickvpnkeys"
  • The string contains the literal escape \U0001F397 (🎗️), followed by the
    raw three bytes
    \xef\xb8\x8f (U+FE0F variation selector) written into the
    file.
  • go-yaml v3's scanner checks every byte with its is_printable() table and
    flags these bytes as control characters → yaml: control characters are not allowed → whole file rejected.
  • PyYAML (used by many local test scripts) is lenient about these bytes, which
    is why the file "passes" some local checks but fails in real Clash.Meta
    clients.
  • Note: earlier quick scans that only look at bytes < 0x20 will NOT catch
    this — the offending bytes are in the C1 range (0x7F–0x9F), which is
    exactly why a byte-scan must replicate yaml.v3's printable check.

This single 3-byte sequence in one node bricks the entire subscription for
Clash.Meta users.


2. Invalid network values (80 nodes)

network: raw is not a valid transport for vless/vmess in Clash.Meta.
Supported values are tcp / udp / ws / grpc / h2 / http / xhttp.
"raw" is a sing-box/Xray internal term and does not exist in Clash.Meta.

Examples (80 total):

- name: vless-262807486
  type: vless
  network: raw          # invalid, should be tcp
- name: vmess-485686543
  type: vmess
  network: raw          # invalid, should be tcp

3. vless/gRPC nodes missing grpc-opts.serviceName (24 nodes)

Clash.Meta requires grpc-opts.serviceName for network: grpc. Without it the
gRPC transport cannot complete the handshake.

- name: vless-330581942
  type: vless
  network: grpc         # missing grpc-opts.serviceName

4. Reality nodes with empty short-id (30 nodes)

REALITY handshake requires a non-empty short-id. Nodes with short-id: ""
can never connect and only pollute the list. Example:

- name: vless-549947446
  type: vless
  tls: true
  reality-opts:
    public-key: kdlgIBl7R_1DIWtJIrOqYwt65nedPCbz6XoomdpQIXM
    short-id: ""        # empty — REALITY can never handshake

5. Invalid sni values — URL / t.me link / emoji (14 nodes)

sni must be a plain hostname (or IP). These nodes put URLs, Telegram links,
or emoji into sni, which breaks the TLS/QUIC handshake:

- name: hysteria2-467420662
  sni: "https://t.me/oneclickvpnkeys"     # URL, not a hostname
- name: hysteria2-467980788
  sni: "https://t.me/clashv8🇨🇳"           # URL + emoji
- name: trojan-429843172
  sni: t.me/ripaojiedian                   # Telegram path
- name: trojan-133769367
  sni: t.me%2fripaojiedian                 # percent-encoded path
- name: hysteria2-483633865
  sni: "https://t.me/wangcai2…"            # contains C1 bytes
- name: hysteria2-483633864
  sni: "https://t.me/wangcai2рҹүёрҹүі"    # mojibake

(6 more nodes use sni: "t.me" alone.)

6. ws nodes missing ws-opts (83 nodes)

vless/vmess with network: ws should carry ws-opts (path, headers.Host).
Without a path the WebSocket upgrade almost always fails. Examples (83 total):

- name: vmess-145260
  type: vmess
  network: ws          # missing ws-opts
- name: vless-463880255
  type: vless
  network: ws          # missing ws-opts

Impact summary

# Issue Count
0 Control bytes in YAML → whole file rejected (fatal) 1 node, 3 bytes
1 network: raw (invalid transport) 80
2 gRPC missing grpc-opts.serviceName 24
3 Reality empty short-id 30
4 Invalid sni (URL / t.me / emoji) 14
5 ws missing ws-opts 83
Total affected ~237 / 1996 (~12%)

Two additional observations worth noting for the generator pipeline:

  • Node names are globally unique (no duplicates) — good.
  • The structure (proxies / proxy-groups / rules) is otherwise
    well-formed; groups reference only existing nodes.

Suggested fixes for the generator

  1. Sanitize output to pure ASCII: escape or strip every non-ASCII byte
    (write \uXXXX escapes or drop emoji) before writing the YAML — this alone
    fixes the fatal import bug.
  2. Normalize network: rawtcp during conversion.
  3. Always emit grpc-opts.serviceName for network: grpc nodes, or drop them.
  4. Skip Reality nodes whose short-id / public-key are empty.
  5. Validate sni is a bare hostname/IP: strip scheme, path, t.me fragments,
    and emoji; fall back to servername/server when invalid.
  6. Always emit ws-opts for ws nodes (at least a default path: /).

Verification

After applying the above, a sanitized profile (ASCII-only, 1966 nodes after
dropping 30 broken Reality nodes) was validated with the real kernel:

$ mihomo -t -f clash.meta.yaml
configuration file clash.meta.yaml test is successful

Happy to share the sanitizing script or a patched clash.yaml if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions