{"record":{"id":"3619ae338ee01555","repo":"sgl-project/sglang","slug":"invalid-transition-for-request-id-old-state-va","errorCode":null,"errorMessage":"Invalid transition for {request_id}: {old_state.value} -> {new_state.value}","messagePattern":"Invalid transition for (.+?): (.+?) -> (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/multimodal_gen/runtime/disaggregation/request_state.py","lineNumber":116,"sourceCode":"        denoiser_instance: int | None = None,\n        decoder_instance: int | None = None,\n    ) -> RequestRecord:\n        with self._lock:\n            record = self._requests.get(request_id)\n            if record is None:\n                raise ValueError(f\"Unknown request_id: {request_id}\")\n\n            old_state = record.state\n\n            if new_state in _TERMINAL_STATES and new_state != RequestState.DONE:\n                # FAILED / TIMED_OUT: allowed from any active state\n                if old_state not in _ACTIVE_STATES:\n                    raise ValueError(\n                        f\"Cannot transition {request_id} from terminal state \"\n                        f\"{old_state.value} to {new_state.value}\"\n                    )\n            elif new_state not in _VALID_TRANSITIONS.get(old_state, set()):\n                raise ValueError(\n                    f\"Invalid transition for {request_id}: \"\n                    f\"{old_state.value} -> {new_state.value}\"\n                )\n\n            record.state = new_state\n            record.last_transition_time = time.monotonic()\n            if error is not None:\n                record.error = error\n            if encoder_instance is not None:\n                record.encoder_instance = encoder_instance\n            if denoiser_instance is not None:\n                record.denoiser_instance = denoiser_instance\n            if decoder_instance is not None:\n                record.decoder_instance = decoder_instance\n\n            logger.debug(\n                \"Request %s: %s -> %s\", request_id, old_state.value, new_state.value\n            )","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/multimodal_gen/runtime/disaggregation/request_state.py#L98-L134","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect record.state before calling transition and skip/log if the edge is invalid for the current lifecycle point","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","If you added a legitimate new edge, add it to _VALID_TRANSITIONS in request_state.py (library change)","Make handlers idempotent so duplicate/replayed events cannot push the state machine backwards"],"exampleFix":"# before\nstore.transition(request_id, RequestState.DECODING)  # illegal edge -> ValueError\n\n# after\nVALID = _VALID_TRANSITIONS.get(record.state, set())\nif RequestState.DECODING in VALID:\n    store.transition(request_id, RequestState.DECODING)\nelse:\n    logger.warning(\"ignoring DECODING for %s in state %s\", request_id, record.state)","handlingStrategy":"validation","validationCode":"from sglang.multimodal_gen.runtime.disaggregation.request_state import _VALID_TRANSITIONS\nrecord = store.get(request_id)\nif record is None or new_state not in _VALID_TRANSITIONS.get(record.state, set()):\n    logger.warning(\"illegal transition %s -> %s for %s; ignoring\",\n                   record.state if record else None, new_state, request_id)\n    return\nstore.transition(request_id, new_state)","typeGuard":"def transition_allowed(current: RequestState, target: RequestState) -> bool:\n    return target in _VALID_TRANSITIONS.get(current, set())","tryCatchPattern":"try:\n    store.transition(request_id, new_state)\nexcept ValueError as e:\n    if \"Invalid transition\" in str(e):\n        logger.warning(\"out-of-order event for %s; current=%s\", request_id, new_state)\n        return\n    raise","preventionTips":["Gate handlers on the current record state instead of frame arrival order","Make event handling idempotent so replays cannot rewind the state machine","When adding states or edges, update _VALID_TRANSITIONS and add state-machine unit tests"],"tags":["disaggregation","request-state","state-machine","invalid-transition"],"backgroundTag":"invalid-state-transition","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}