unslothai/unsloth · warning · RuntimeError

server_lifecycle_changed

Error message

server_lifecycle_changed

What it means

Raised by start_remote_access() when the current studio tunnel control token no longer matches the admission captured at function entry. The tunnel controller rotates its token on lifecycle events (restart, new controller generation), so a mismatch means the world changed between capturing admission and acting on it. The call is aborted rather than acting on a stale generation.

Source

Thrown at studio/backend/utils/remote_access_settings.py:265

        "streaming_supported": status["url"] is None,
    }


def start_remote_access(app_state) -> dict:
    """Schedule a settings-owned start. Repeated requests are idempotent."""
    global _start_worker, _start_worker_admission
    from cloudflare_tunnel import (
        capture_studio_tunnel_start_admission,
        get_studio_tunnel_control_token,
    )

    admission = capture_studio_tunnel_start_admission()
    if admission is None:
        raise RuntimeError("server_shutting_down")
    status = remote_access_status(app_state)
    current = get_studio_tunnel_control_token()
    if current[0] != admission[0]:
        raise RuntimeError("server_lifecycle_changed")
    if status["managed_by"] == "settings" and status["state"] in {"starting", "online"}:
        return status
    if not status["can_start"]:
        raise RuntimeError(status["block_reason"] or "operation_in_progress")

    port = getattr(app_state, "remote_access_port", None)
    if not isinstance(port, int) or port <= 0:
        raise RuntimeError("server_port_unavailable")
    if get_studio_tunnel_control_token() != admission:
        raise RuntimeError("server_lifecycle_changed")

    def _start() -> None:
        from cloudflare_tunnel import start_studio_tunnel
        url = start_studio_tunnel(port, managed_by = "settings", admission = admission)
        if url:
            logger.info("Secure link access via Cloudflare: %s", url)

    _open_remote_access_stop_response_admission()

View on GitHub (pinned to 203007d190)

Solutions

  1. Re-read the current tunnel status and retry the start — the new generation will mint a fresh matching admission.
  2. Serialize remote-access settings operations client-side (disable the Start/Stop buttons while one is in flight) to avoid racing requests.
  3. If it recurs constantly, inspect logs for what keeps rotating the control token (crash loop, repeated restarts).
Defensive patterns

Strategy: retry

Try / catch

for attempt in range(3):
    try:
        return start_remote_access(app_state)
    except RuntimeError as e:
        if str(e) == 'server_lifecycle_changed' and attempt < 2:
            time.sleep(0.5 * (attempt + 1))
            continue
        raise

Prevention

When it happens

Trigger: Calling start_remote_access() and, between capture_studio_tunnel_start_admission() and the token comparison, another lifecycle event rotates the control token — e.g. a concurrent stop/restart, controller regeneration, or shutdown racing the start request.

Common situations: Two settings requests racing (start and stop clicked in quick succession), a restart triggered while a start is in flight, or automation firing start during an update. Essentially a TOCTOU guard on tunnel generation.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/07099e1ed71b1c23. Report an issue: GitHub.