sgl-project/sglang · error · ValueError

Invalid transition for {request_id}: {old_state.value} -> {n

Error message

Invalid transition for {request_id}: {old_state.value} -> {new_state.value}

What it means

Raised by RequestStateStore.transition when the requested state change is not in _VALID_TRANSITIONS for the current state — the request lifecycle state machine (e.g. PENDING -> DISPATCHED -> ENCODING -> TRANSFER_STAGED -> DECODING -> DONE) only permits specific edges. Any out-of-order or illegal edge (e.g. PENDING straight to DONE, DECODING back to ENCODING) triggers this.

Source

Thrown at python/sglang/multimodal_gen/runtime/disaggregation/request_state.py:116

        denoiser_instance: int | None = None,
        decoder_instance: int | None = None,
    ) -> RequestRecord:
        with self._lock:
            record = self._requests.get(request_id)
            if record is None:
                raise ValueError(f"Unknown request_id: {request_id}")

            old_state = record.state

            if new_state in _TERMINAL_STATES and new_state != RequestState.DONE:
                # FAILED / TIMED_OUT: allowed from any active state
                if old_state not in _ACTIVE_STATES:
                    raise ValueError(
                        f"Cannot transition {request_id} from terminal state "
                        f"{old_state.value} to {new_state.value}"
                    )
            elif new_state not in _VALID_TRANSITIONS.get(old_state, set()):
                raise ValueError(
                    f"Invalid transition for {request_id}: "
                    f"{old_state.value} -> {new_state.value}"
                )

            record.state = new_state
            record.last_transition_time = time.monotonic()
            if error is not None:
                record.error = error
            if encoder_instance is not None:
                record.encoder_instance = encoder_instance
            if denoiser_instance is not None:
                record.denoiser_instance = denoiser_instance
            if decoder_instance is not None:
                record.decoder_instance = decoder_instance

            logger.debug(
                "Request %s: %s -> %s", request_id, old_state.value, new_state.value
            )

View on GitHub (pinned to 0132848349)

Solutions

  1. Inspect record.state before calling transition and skip/log if the edge is invalid for the current lifecycle point
  2. Fix the ordering: ensure handlers fire in lifecycle order (dispatch before encoding, staging before decode), e.g. by gating on state rather than arrival order
  3. If you added a legitimate new edge, add it to _VALID_TRANSITIONS in request_state.py (library change)
  4. Make handlers idempotent so duplicate/replayed events cannot push the state machine backwards

Example fix

# before
store.transition(request_id, RequestState.DECODING)  # illegal edge -> ValueError

# after
VALID = _VALID_TRANSITIONS.get(record.state, set())
if RequestState.DECODING in VALID:
    store.transition(request_id, RequestState.DECODING)
else:
    logger.warning("ignoring DECODING for %s in state %s", request_id, record.state)
Defensive patterns

Strategy: validation

Validate before calling

from sglang.multimodal_gen.runtime.disaggregation.request_state import _VALID_TRANSITIONS
record = store.get(request_id)
if record is None or new_state not in _VALID_TRANSITIONS.get(record.state, set()):
    logger.warning("illegal transition %s -> %s for %s; ignoring",
                   record.state if record else None, new_state, request_id)
    return
store.transition(request_id, new_state)

Type guard

def transition_allowed(current: RequestState, target: RequestState) -> bool:
    return target in _VALID_TRANSITIONS.get(current, set())

Try / catch

try:
    store.transition(request_id, new_state)
except ValueError as e:
    if "Invalid transition" in str(e):
        logger.warning("out-of-order event for %s; current=%s", request_id, new_state)
        return
    raise

Prevention

When it happens

Trigger: Calling transition(id, new_state) with an edge absent from _VALID_TRANSITIONS[old_state] — e.g. _dispatch_to_encoder firing after the request already moved to DECODING, or skipping a required intermediate state because a handler ran out of order.

Common situations: Reordered async handlers (transfer completion racing encoder dispatch), duplicated event frames processed at the wrong lifecycle point, adding a new state/handler without updating _VALID_TRANSITIONS, or resuming a request from a checkpoint at the wrong recorded state.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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