sgl-project/sglang · error · ValueError

{kind} transition actions must be a list

Error message

{kind} transition actions must be a list

What it means

Raised while parsing a control-state event payload: each transition in the event's transitions list must itself carry an `actions` key whose value is a JSON list. The parser (called from parse_control_event_payload) refuses anything else so downstream normalize_state_payload always receives a list.

Source

Thrown at python/sglang/multimodal_gen/runtime/realtime/control_signals.py:127


def _control_state_transitions_from_event_payload(
    payload: dict[str, Any],
    *,
    event_id: int | None,
    kind: str,
    normalize_state_payload: ControlStatePayloadNormalizer,
) -> list[ControlStateTransition]:
    transitions = payload.get("transitions")
    if not isinstance(transitions, list):
        raise ValueError(f"{kind} state payload requires transitions")
    result = []
    for transition in transitions:
        if not isinstance(transition, dict):
            raise ValueError(f"{kind} transition must be a map")
        actions = transition.get("actions")
        if not isinstance(actions, list):
            raise ValueError(f"{kind} transition actions must be a list")
        timestamp_ms = transition.get("client_ts_ms")
        if timestamp_ms is not None:
            timestamp_ms = int(timestamp_ms)
        result.append(
            ControlStateTransition(
                payload=normalize_state_payload(actions),
                seq_id=event_id,
                timestamp_ms=timestamp_ms,
            )
        )
    return result


class ControlSignalQueue:
    """FIFO storage for discrete realtime control signals

    Script-mode controls and one-shot signals are already expressed as discrete
    payloads, so sampling only consumes queued signals and applies the requested

View on GitHub (pinned to 0132848349)

Solutions

  1. Fix the producer of the control event so each transition's actions is a JSON array (e.g. [{"type": ...}, ...]).
  2. If actions legitimately arrives as an object, coerce it to a list before calling parse_control_event_payload.
  3. Add a schema check (jsonschema/pydantic) at the event boundary so malformed events fail loudly with a clearer message.

Example fix

// before
{"transitions": [{"client_ts_ms": 10, "actions": {"mic": "on"}}]}
// after
{"transitions": [{"client_ts_ms": 10, "actions": ["mic:on"]}]}
Defensive patterns

Strategy: validation

Validate before calling

for t in payload.get('transitions', []):
    if not isinstance(t.get('actions'), list):
        raise ValueError('transition actions must be a list before parsing')

Type guard

def has_valid_transitions(payload: dict) -> bool:
    return isinstance(payload.get('transitions'), list) and all(
        isinstance(t, dict) and isinstance(t.get('actions'), list)
        for t in payload['transitions']
    )

Try / catch

try:
    parse_control_event_payload(raw)
except ValueError as e:
    drop_or_alert_malformed_event(raw, e)

Prevention

When it happens

Trigger: Calling parse_control_event_payload with a payload where transitions[i]['actions'] is missing, null, a dict, or a string instead of a JSON array.

Common situations: Upstream producer changes the control-event schema (actions renamed, emitted as an object map keyed by action name); hand-crafted test payloads omitting actions; JSON where actions is null.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/1deab690bb4b972e. Report an issue: GitHub.