unslothai/unsloth · warning · HTTPException

The requested training job is no longer active.

Error message

The requested training job is no longer active.

What it means

HTTP 409 from the stop endpoint when the stop operation returns outcome=='superseded': the job identified by expected_job_id is no longer the active job. The stop helper checks the caller's expected_job_id against the backend's current job before stopping, so a stale client cannot stop a newer run it did not see.

Source

Thrown at studio/backend/routes/training.py:1768

):
    """
    Stop the currently running training job.

    Body:
        save (bool): If True (default), save the model at the current checkpoint.
        expected_job_id (str): Identifier of the job the caller intends to stop.
    """
    try:
        backend = get_training_backend()
        outcome = await asyncio.to_thread(
            _stop_training_if_active,
            backend,
            save = body.save,
            expected_job_id = body.expected_job_id,
        )
        logger.info("Stop requested: save=%s outcome=%s", body.save, outcome)
        if outcome == "superseded":
            raise HTTPException(
                status_code = 409,
                detail = "The requested training job is no longer active.",
            )
        if outcome == "idle":
            return TrainingStopResponse(
                status = "idle", message = "No training job is currently running"
            )

        return TrainingStopResponse(
            status = "stopped",
            message = "Stop requested. Training will stop at the next safe step.",
        )

    except HTTPException:
        raise
    except Exception as e:
        raise log_and_http_error(
            e,

View on GitHub (pinned to 203007d190)

Solutions

  1. Refetch the current training status to obtain the active job_id.
  2. If no training is running, treat the stop as already satisfied — call stop without expected_job_id or handle the 'idle' response.
  3. Only send expected_job_id when you genuinely intend 'stop exactly this run'.

Example fix

// before
await post('/training/stop', {save: true, expected_job_id: staleId})
// after
const status = await get('/training/status')
if (status.job_id) {
  await post('/training/stop', {save: true, expected_job_id: status.job_id})
}
Defensive patterns

Strategy: validation

Validate before calling

const status = await get('/training/status')
const target = status.job_id
if (!target) { console.log('No active job to stop'); return }
await post('/training/stop', {save: true, expected_job_id: target})

Try / catch

try { await post('/training/stop', body) } catch (e) { if (e.status === 409 && /no longer active/.test(e.detail)) { const s = await get('/training/status'); if (s.job_id) return post('/training/stop', {save: body.save, expected_job_id: s.job_id}) ; return } throw e }

Prevention

When it happens

Trigger: POST /training/stop with expected_job_id set to a job that already finished, was reset, or was replaced by a newly spawned training job.

Common situations: A UI that holds an old job id after a page reload or after another tab started a new run; automation polling a finished job and then issuing stop with the stale id.

Related errors


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