{"record":{"id":"8d0f9a7bfa7b9e69","repo":"vllm-project/vllm","slug":"malformed-zmq-address-zmq-address-r-expected-h","errorCode":null,"errorMessage":"Malformed zmq_address {zmq_address!r}: expected 'host:IP,handshake:PORT,notify:PORT' format","messagePattern":"Malformed zmq_address (.+?): expected 'host:IP,handshake:PORT,notify:PORT' format","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_common.py","lineNumber":411,"sourceCode":"\n    Parses ``\"host:IP,handshake:PORT,notify:PORT\"`` into\n        (host, handshake_port, notify_port).\n\n    Each key-value pair is split on the *first* colon so that IPv6 addresses\n    (e.g. ``host:::1``) are handled correctly.  Raises ``ValueError`` if any\n    of ``host``, ``handshake``, or ``notify`` keys are absent or if the port\n    values are non-numeric.\n    \"\"\"\n    parts: dict[str, str] = {}\n    for segment in zmq_address.split(\",\"):\n        key, _, val = segment.partition(\":\")\n        parts[key.strip()] = val.strip()\n    try:\n        host = parts[\"host\"]\n        handshake_port = int(parts[\"handshake\"])\n        notify_port = int(parts[\"notify\"])\n    except (KeyError, ValueError) as e:\n        raise ValueError(\n            f\"Malformed zmq_address {zmq_address!r}: expected \"\n            f\"'host:IP,handshake:PORT,notify:PORT' format\"\n        ) from e\n    return host, handshake_port, notify_port\n\n\ndef get_peer_zmq_from_request_id(request_id: str, is_producer: bool) -> str | None:\n    \"\"\"Extract the *peer's* zmq_address from the vLLM router request_id.\n\n    The producer (prefill) needs the decode's address; the consumer (decode)\n    needs the prefill's address.\n\n    Returns ``None`` when the request_id does not encode peer info. The\n    llm-d routing sidecar (``llm-d-inference-scheduler``) does not embed\n    addresses in ``request_id``; instead it passes ``remote_host``,\n    ``remote_handshake_port`` and ``remote_notify_port`` explicitly in\n    ``kv_transfer_params``. Callers must handle the ``None`` return by\n    falling back to those fields. See ``add_new_req`` for the canonical","sourceCodeStart":393,"sourceCodeEnd":429,"githubUrl":"https://github.com/vllm-project/vllm/blob/c794754062d49a8fdb63ab3c5215b488b865030c/vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_common.py#L393-L429","documentation":"Thrown by parse_moriio_zmq_address in the MoRI-IO KV-transfer connector when the zmq_address string cannot be parsed. The connector requires the exact comma-separated form 'host:IP,handshake:PORT,notify:PORT'; a missing host/handshake/notify key (KeyError) or a non-numeric port (ValueError) is re-raised as this ValueError.","triggerScenarios":"parse_moriio_zmq_address (directly, or via get_peer_zmq_from_request_id / build paths in moriio_common.py) is called with a string that omits one of the three key:value segments, has an empty host, or has non-integer ports. Common trigger: the router embeds a differently-formatted address into the request_id and the peer side parses it.","commonSituations":"Custom or older router versions that embed a different request_id format; passing a raw ZMQ endpoint like 'tcp://10.0.0.1:5555' instead of the key:value pair format; hand-typed kv_transfer_params with typos or missing ports; extra segments that overwrite expected keys.","solutions":["Log the offending zmq_address and diff it against 'host:IP,handshake:PORT,notify:PORT' (all three keys, integer ports)","Fix the producer side (router or prefill instance) that generates/embeds the address so it emits all three segments correctly","If the address is supplied via kv_transfer_params, correct remote_host / remote_handshake_port / remote_notify_port there","Add a format check where the address is produced so malformed values fail at the source instead of at transfer time"],"exampleFix":"// before\nzmq_address = \"tcp://10.0.0.5:5555\"  # wrong format, raises on parse\nhost, hs, nf = parse_moriio_zmq_address(zmq_address)\n\n// after\nzmq_address = \"host:10.0.0.5,handshake:5555,notify:5556\"\nhost, hs, nf = parse_moriio_zmq_address(zmq_address)","handlingStrategy":"validation","validationCode":"import re\n\nZMQ_ADDR_RE = re.compile(\n    r\"^\\s*host\\s*:\\s*[^,\\s]+\\s*,\\s*handshake\\s*:\\s*\\d+\\s*,\\s*notify\\s*:\\s*\\d+\\s*$\"\n)\n\ndef is_valid_moriio_zmq_address(addr: str) -> bool:\n    return bool(ZMQ_ADDR_RE.match(addr))\n\n# before parsing:\nassert is_valid_moriio_zmq_address(zmq_address), f\"bad zmq_address {zmq_address!r}\"","typeGuard":"def is_moriio_zmq_address(v) -> bool:\n    if not isinstance(v, str):\n        return False\n    parts = dict(s.partition(\":\")[::2] for s in v.split(\",\"))\n    try:\n        return bool(parts.get(\"host\", \"\").strip()) and int(parts[\"handshake\"]) > 0 and int(parts[\"notify\"]) > 0\n    except (KeyError, ValueError):\n        return False","tryCatchPattern":"try:\n    host, hs_port, nf_port = parse_moriio_zmq_address(zmq_address)\nexcept ValueError as e:\n    raise ValueError(f\"routing config error for request {request_id}: {e}\") from e","preventionTips":["Generate zmq_address strings from (host, handshake_port, notify_port) variables instead of concatenating by hand","Validate the format at the producer/router before embedding into request_id","Add a unit test asserting the parser rejects partial and raw-tcp:// forms"],"tags":["zmq","kv-transfer","config","validation"],"backgroundTag":null,"analyzedSha":"c794754062d49a8fdb63ab3c5215b488b865030c","analyzedAt":"2026-08-14T21:17:39.825Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}