unslothai/unsloth · warning · HTTPException
Training start request no longer owns the current job
Error message
Training start request no longer owns the current job
What it means
HTTP 409 when cancel_start_request returns outcome 'superseded': the start request being cancelled no longer owns the current job (a newer start request has taken over the job slot), so cancelling it would not stop anything.
Source
Thrown at studio/backend/routes/training.py:1122
async def cancel_training_start_request(
start_request_id: str = ApiPath(
...,
min_length = 1,
max_length = 128,
pattern = TRAINING_REQUEST_ID_PATTERN,
),
current_subject: str = Depends(get_current_subject),
):
backend = get_training_backend()
try:
outcome, record = await asyncio.to_thread(
backend.cancel_start_request,
start_request_id,
)
except TrainingStartCancellationCapacityError as exc:
raise HTTPException(status_code = 429, detail = str(exc)) from exc
if outcome == "superseded":
raise HTTPException(
status_code = 409,
detail = "Training start request no longer owns the current job",
)
return _start_request_status_response(record)
def _background_video_generation_active() -> bool:
"""Whether a video clip is generating on the video backend's worker thread.
POST /video/generate returns at once and generates in the background, so an
in-flight clip is invisible to the keep-warm in-flight request count the
API-key training guards consult; ask the backend directly. Best-effort: a
probe failure must never block a training start."""
try:
from core.inference.video import get_video_backend
return bool(get_video_backend().generate_progress().get("active"))
except Exception as e: # noqa: BLE001
logger.warning("Could not check video generation state for training guard: %s", e)View on GitHub (pinned to 203007d190)
Solutions
- Fetch the current job id from the backend (job/status endpoints) and cancel via the current owning start request
- If the goal was 'stop whatever is training', cancel the newest start request or the current job directly
- Refresh cached start_request_id after every new /training/start call
Example fix
// before
client.post(f"/training/start-requests/{old_id}/cancel") # 409 superseded
// after
job = client.get("/training/current-job").json()
owner_id = job["start_request_id"]
client.post(f"/training/start-requests/{owner_id}/cancel") Defensive patterns
Strategy: validation
Validate before calling
def cancel_is_meaningful(status: dict) -> bool:
# only cancel the request that currently owns the job
return status.get("state") not in ("superseded", "completed", "failed", "cancelled") Try / catch
resp = client.post(f"/training/start-requests/{rid}/cancel")
if resp.status_code == 409 and "no longer owns" in resp.text:
current = client.get("/training/current-job").json()
rid = current["start_request_id"] # cancel the actual owner Prevention
- Refresh the owning start_request_id before every cancel
- Discard cached ids whenever a new /training/start succeeds
- Treat 'superseded' as benign: the old request needs no cancellation
When it happens
Trigger: POST cancel on an old start_request_id after a newer training start request has already replaced it as the owner of the current job.
Common situations: Client holds a stale id after re-submitting training start; concurrent automation where one flow starts a new job while another tries to cancel the old request.
Related errors
- GGUF STT model loading was cancelled so training could start
- STT model loading was cancelled so training could start.
- The source run contains invalid training configuration and c
- Training start request is not ready to acknowledge
- Too many training start cancellations are pending
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/789963a99a0b4a61.
Report an issue: GitHub.